//! Request handling and routing
//!
//! Routes JSON-RPC requests to appropriate method handlers

use serde_json::Value;
use std::future::Future;
use std::pin::Pin;

use crate::protocol::errors::McpError;
use crate::protocol::methods::McpMethod;
use crate::protocol::types::{JsonRpcError, JsonRpcRequest, JsonRpcResponse};
use crate::server::session::Session;

/// Result type for handlers
pub type HandlerResult = Result<Value, McpError>;

/// Future type for async handlers
pub type HandlerFuture = Pin<Box<dyn Future<Output = HandlerResult> + Send>>;

/// Request context passed to handlers
#[derive(Clone)]
pub struct RequestContext {
    /// The session associated with this request
    pub session: Session,

    /// The JSON-RPC request
    pub request: JsonRpcRequest,
}

impl RequestContext {
    /// Create new request context
    pub fn new(session: Session, request: JsonRpcRequest) -> Self {
        Self { session, request }
    }

    /// Get request parameters
    pub fn params(&self) -> Option<&Value> {
        self.request.params.as_ref()
    }

    /// Get request method
    pub fn method(&self) -> McpMethod {
        McpMethod::from(self.request.method.clone())
    }
}

/// Validates that session is initialized for methods that require it
pub fn require_initialization(ctx: &RequestContext) -> Result<(), McpError> {
    let method = ctx.method();

    // Methods that don't require initialization
    if matches!(method, McpMethod::Initialize | McpMethod::Ping) {
        return Ok(());
    }

    if !ctx.session.is_initialized() {
        return Err(McpError::validation(
            "not_initialized",
            "Session must be initialized first",
        ));
    }

    Ok(())
}

/// Builds a success response
pub fn success_response(request_id: Option<Value>, result: Value) -> JsonRpcResponse {
    JsonRpcResponse {
        jsonrpc: "2.0".to_string(),
        id: request_id.unwrap_or(Value::Null),
        result: Some(result),
        error: None,
    }
}

/// Builds an error response
pub fn error_response(request_id: Option<Value>, error: JsonRpcError) -> JsonRpcResponse {
    JsonRpcResponse {
        jsonrpc: "2.0".to_string(),
        id: request_id.unwrap_or(Value::Null),
        result: None,
        error: Some(error),
    }
}

/// Builds a method not found error response
pub fn method_not_found(request_id: Option<Value>, method: &str) -> JsonRpcResponse {
    error_response(
        request_id,
        JsonRpcError {
            code: -32601,
            message: format!("Method not found: {}", method),
            data: None,
        },
    )
}

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

    #[test]
    fn test_request_context() {
        let session = Session::new();
        let request = JsonRpcRequest {
            jsonrpc: "2.0".to_string(),
            id: Some(Value::Number(1.into())),
            method: "tools/list".to_string(),
            params: Some(serde_json::json!({"cursor": "abc"})),
        };

        let ctx = RequestContext::new(session.clone(), request.clone());

        assert_eq!(ctx.session.id, session.id);
        assert_eq!(ctx.request.method, "tools/list");
        assert_eq!(ctx.method(), McpMethod::ToolsList);
        assert!(ctx.params().is_some());
    }

    #[test]
    fn test_require_initialization() {
        let session = Session::new();

        // Initialize method doesn't require initialization
        let request = JsonRpcRequest {
            jsonrpc: "2.0".to_string(),
            id: Some(Value::Number(1.into())),
            method: "initialize".to_string(),
            params: None,
        };
        let ctx = RequestContext::new(session.clone(), request);
        assert!(require_initialization(&ctx).is_ok());

        // Ping method doesn't require initialization
        let request = JsonRpcRequest {
            jsonrpc: "2.0".to_string(),
            id: Some(Value::Number(2.into())),
            method: "ping".to_string(),
            params: None,
        };
        let ctx = RequestContext::new(session.clone(), request);
        assert!(require_initialization(&ctx).is_ok());

        // Other methods require initialization
        let request = JsonRpcRequest {
            jsonrpc: "2.0".to_string(),
            id: Some(Value::Number(3.into())),
            method: "tools/list".to_string(),
            params: None,
        };
        let ctx = RequestContext::new(session.clone(), request);
        assert!(require_initialization(&ctx).is_err());

        // After initialization, should succeed
        let mut session = Session::new();
        session.initialize(
            crate::protocol::types::Implementation::new("test", "1.0"),
            Default::default(),
            "2025-06-18".to_string(),
        );
        let request = JsonRpcRequest {
            jsonrpc: "2.0".to_string(),
            id: Some(Value::Number(4.into())),
            method: "tools/list".to_string(),
            params: None,
        };
        let ctx = RequestContext::new(session, request);
        assert!(require_initialization(&ctx).is_ok());
    }

    #[test]
    fn test_success_response() {
        let response = success_response(
            Some(Value::Number(1.into())),
            serde_json::json!({"status": "ok"}),
        );

        assert_eq!(response.jsonrpc, "2.0");
        assert_eq!(response.id, Value::Number(1.into()));
        assert!(response.result.is_some());
        assert!(response.error.is_none());
    }

    #[test]
    fn test_error_response() {
        let error = JsonRpcError {
            code: -32600,
            message: "Invalid request".to_string(),
            data: None,
        };
        let response = error_response(Some(Value::Number(1.into())), error);

        assert_eq!(response.jsonrpc, "2.0");
        assert_eq!(response.id, Value::Number(1.into()));
        assert!(response.result.is_none());
        assert!(response.error.is_some());
        assert_eq!(response.error.unwrap().code, -32600);
    }

    #[test]
    fn test_method_not_found() {
        let response = method_not_found(Some(Value::Number(1.into())), "unknown/method");

        assert!(response.error.is_some());
        let error = response.error.unwrap();
        assert_eq!(error.code, -32601);
        assert!(error.message.contains("unknown/method"));
    }
}
