//! Request and response types for gateway API handlers.

use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::Json;
use serde::{Deserialize, Serialize};

use crate::sharding::ShardId;

/// Request to create a new assertion.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateAssertionRequest {
    /// Subject of the assertion (used for shard routing).
    pub subject: String,

    /// Predicate of the assertion.
    pub predicate: String,

    /// Object value of the assertion.
    pub object: serde_json::Value,

    /// Ed25519 signature (base64 encoded).
    pub signature: String,

    /// Public key of the signer (base64 encoded).
    pub public_key: String,
}

/// Response from assertion creation.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssertionResponse {
    /// ID of the created assertion (content hash).
    pub assertion_id: String,

    /// Shard the assertion was routed to.
    pub shard_id: ShardId,

    /// Node that processed the write.
    pub leader_node: String,
}

/// Query parameters for assertion lookup.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryParams {
    /// Subject to query.
    pub subject: String,

    /// Optional predicate filter.
    pub predicate: Option<String>,

    /// Optional lens for resolution.
    pub lens: Option<String>,

    /// Maximum results to return.
    pub limit: Option<usize>,
}

/// Query response with assertions.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QueryResponse {
    /// Matching assertions.
    pub assertions: Vec<serde_json::Value>,

    /// Shard that served the query.
    pub shard_id: ShardId,

    /// Node that served the query.
    pub served_by: String,
}

/// Vote request.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoteRequest {
    /// Subject being voted on.
    pub subject: String,

    /// ID of assertion being voted for.
    pub assertion_id: String,

    /// Vote weight (positive or negative).
    pub weight: i64,

    /// Voter's signature.
    pub signature: String,

    /// Voter's public key.
    pub public_key: String,
}

/// Vote response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoteResponse {
    /// Whether the vote was recorded.
    pub success: bool,

    /// Shard that processed the vote.
    pub shard_id: ShardId,
}

/// Cluster status response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterStatusResponse {
    /// Number of nodes in cluster.
    pub node_count: usize,

    /// Number of shards.
    pub shard_count: u32,

    /// Meta-range version.
    pub meta_version: u64,

    /// Individual node statuses.
    pub nodes: Vec<NodeStatusInfo>,
}

/// Status of a single node.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeStatusInfo {
    /// Node ID (short hex).
    pub id: String,

    /// Node state.
    pub state: String,

    /// Shards this node is responsible for.
    pub shards: Vec<ShardId>,
}

/// Health check response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthResponse {
    /// Whether the gateway is healthy.
    pub healthy: bool,

    /// Number of reachable nodes.
    pub reachable_nodes: usize,

    /// Whether the local node has joined the cluster.
    pub joined: bool,
}

/// API error response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiError {
    /// Error code.
    pub code: String,

    /// Human-readable message.
    pub message: String,
}

impl IntoResponse for ApiError {
    fn into_response(self) -> axum::response::Response {
        let status = match self.code.as_str() {
            "NOT_FOUND" => StatusCode::NOT_FOUND,
            "BAD_REQUEST" => StatusCode::BAD_REQUEST,
            "UNAVAILABLE" => StatusCode::SERVICE_UNAVAILABLE,
            "NOT_IMPLEMENTED" => StatusCode::NOT_IMPLEMENTED,
            _ => StatusCode::INTERNAL_SERVER_ERROR,
        };

        (status, Json(self)).into_response()
    }
}

/// Response for GET /v1/admin/ranges - All range/shard assignments.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RangesResponse {
    /// List of all range descriptors.
    pub ranges: Vec<RangeInfoDto>,

    /// Total number of ranges.
    pub total_ranges: usize,
}

/// Information about a single range/shard.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RangeInfoDto {
    /// Range identifier (shard ID).
    pub range_id: String,

    /// Start key (hex-encoded, empty if unbounded).
    pub start_key: String,

    /// End key (hex-encoded, empty if unbounded).
    pub end_key: String,

    /// Size in bytes.
    pub size_bytes: u64,

    /// Number of assertions in this range.
    pub assertion_count: u64,

    /// Leader node for writes (short hex).
    pub leader_node: Option<String>,

    /// Replica nodes (short hex).
    pub replica_nodes: Vec<String>,

    /// Range generation number (increments on rebalance).
    pub generation: u64,
}

/// Request for POST /v1/admin/sync - Force anti-entropy sync.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ForceSyncRequest {
    /// Optional peer ID to sync with. None = sync with all peers.
    pub peer_id: Option<String>,
}

/// Response for POST /v1/admin/sync.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ForceSyncResponse {
    /// Whether the sync was triggered.
    pub triggered: bool,

    /// Number of peers notified.
    pub peers_notified: usize,

    /// Human-readable message.
    pub message: String,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_api_error_response() {
        let err =
            ApiError { code: "NOT_FOUND".to_string(), message: "Resource not found".to_string() };

        let response = err.into_response();
        assert_eq!(response.status(), StatusCode::NOT_FOUND);
    }

    #[test]
    fn test_create_assertion_request_serde() {
        let req = CreateAssertionRequest {
            subject: "test:subject".to_string(),
            predicate: "schema:name".to_string(),
            object: serde_json::json!("Test Name"),
            signature: "sig123".to_string(),
            public_key: "pk456".to_string(),
        };

        let json = serde_json::to_string(&req).unwrap();
        let parsed: CreateAssertionRequest = serde_json::from_str(&json).unwrap();

        assert_eq!(parsed.subject, req.subject);
        assert_eq!(parsed.predicate, req.predicate);
    }
}
