//! Transport layer abstraction for ACP (Agent-Client Protocol).
//!
//! This module provides a unified interface for communicating with ACP agents
//! over different transports (stdio, HTTP+SSE). It handles JSON-RPC message
//! encoding/decoding and transport-specific connection management.

use async_trait::async_trait;
use futures::Stream;
use serde::{Deserialize, Serialize};
use std::pin::Pin;
use thiserror::Error;

// Shared utilities for JSON-over-stdio transports
pub mod json_reader;

/// JSON-RPC 2.0 request message.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
    pub jsonrpc: String, // Always "2.0"
    pub id: serde_json::Value, // Request ID (number, string, or null)
    pub method: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub params: Option<serde_json::Value>,
}

impl JsonRpcRequest {
    /// Create a new JSON-RPC request with auto-generated numeric ID.
    pub fn new(id: u64, method: impl Into<String>, params: Option<serde_json::Value>) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            id: serde_json::Value::Number(id.into()),
            method: method.into(),
            params,
        }
    }

    /// Create a new JSON-RPC request with string ID.
    pub fn new_with_string_id(
        id: impl Into<String>,
        method: impl Into<String>,
        params: Option<serde_json::Value>,
    ) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            id: serde_json::Value::String(id.into()),
            method: method.into(),
            params,
        }
    }

    /// Create a JSON-RPC notification (no ID field).
    ///
    /// Notifications are fire-and-forget - no response is expected.
    pub fn notification(method: impl Into<String>, params: Option<serde_json::Value>) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            id: serde_json::Value::Null,
            method: method.into(),
            params,
        }
    }
}

/// JSON-RPC 2.0 response message (success).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
    pub jsonrpc: String, // Always "2.0"
    pub id: serde_json::Value, // Request ID (matches request)
    pub result: serde_json::Value,
}

/// JSON-RPC 2.0 error response.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcErrorResponse {
    pub jsonrpc: String, // Always "2.0"
    pub id: serde_json::Value, // Request ID (matches request)
    pub error: JsonRpcError,
}

/// JSON-RPC 2.0 error object.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
    pub code: i32,
    pub message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<serde_json::Value>,
}

impl std::fmt::Display for JsonRpcError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "code {}: {}", self.code, self.message)
    }
}

/// JSON-RPC 2.0 notification message (no response expected).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcNotification {
    pub jsonrpc: String, // Always "2.0"
    pub method: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub params: Option<serde_json::Value>,
}

impl JsonRpcNotification {
    /// Create a new JSON-RPC notification.
    pub fn new(method: impl Into<String>, params: Option<serde_json::Value>) -> Self {
        Self {
            jsonrpc: "2.0".to_string(),
            method: method.into(),
            params,
        }
    }
}

/// Transport-level errors.
#[derive(Debug, Error)]
pub enum TransportError {
    #[error("Connection error: {0}")]
    ConnectionError(String),

    #[error("I/O error: {0}")]
    IoError(#[from] std::io::Error),

    #[error("JSON serialization error: {0}")]
    SerializationError(#[from] serde_json::Error),

    #[error("JSON-RPC error: {0}")]
    JsonRpcError(JsonRpcError),

    #[error("Transport closed")]
    Closed,

    #[error("Timeout waiting for response")]
    Timeout,

    #[error("Process exited unexpectedly")]
    ProcessExited,

    #[error("HTTP error: {0}")]
    HttpError(String),

    #[error("SSE error: {0}")]
    SseError(String),

    #[error("Other error: {0}")]
    Other(String),
}

/// Transport state.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportState {
    /// Not yet connected.
    Disconnected,
    /// Currently connecting.
    Connecting,
    /// Connected and operational.
    Connected,
    /// Disconnected due to error.
    Error,
}


/// Result type for JSON-RPC operations.
///
/// This wraps either a successful response or an error response.
#[derive(Debug, Clone)]
pub enum JsonRpcResult {
    Success(JsonRpcResponse),
    Error(JsonRpcErrorResponse),
}

impl JsonRpcResult {
    /// Convert to Result, mapping error responses to TransportError.
    pub fn into_result(self) -> Result<JsonRpcResponse, TransportError> {
        match self {
            JsonRpcResult::Success(response) => Ok(response),
            JsonRpcResult::Error(error_response) => Err(TransportError::JsonRpcError(error_response.error)),
        }
    }

    /// Get the result value if successful.
    pub fn result(&self) -> Option<&serde_json::Value> {
        match self {
            JsonRpcResult::Success(response) => Some(&response.result),
            JsonRpcResult::Error(_) => None,
        }
    }

    /// Get the error if this is an error response.
    pub fn error(&self) -> Option<&JsonRpcError> {
        match self {
            JsonRpcResult::Success(_) => None,
            JsonRpcResult::Error(response) => Some(&response.error),
        }
    }

    /// Check if this is a success response.
    pub fn is_success(&self) -> bool {
        matches!(self, JsonRpcResult::Success(_))
    }

    /// Check if this is an error response.
    pub fn is_error(&self) -> bool {
        matches!(self, JsonRpcResult::Error(_))
    }
}

/// Notification stream type.
pub type NotificationStream = Pin<Box<dyn Stream<Item = JsonRpcNotification> + Send>>;

/// Transport trait for ACP communication.
///
/// This trait abstracts over stdio and HTTP transports, providing a unified
/// interface for sending JSON-RPC requests and receiving notifications.
#[async_trait]
pub trait Transport: Send + Sync {
    /// Send a JSON-RPC request and wait for the response.
    ///
    /// This is a request-response operation. The transport will match the
    /// response by request ID.
    async fn send_request(&mut self, request: JsonRpcRequest) -> Result<JsonRpcResult, TransportError>;

    /// Send a JSON-RPC notification (fire-and-forget).
    ///
    /// Notifications do not expect a response.
    async fn send_notification(&mut self, notification: JsonRpcNotification) -> Result<(), TransportError>;

    /// Get a stream of notifications from the agent.
    ///
    /// This returns a stream that yields notifications as they arrive.
    fn notification_stream(&mut self) -> NotificationStream;

    /// Close the transport.
    ///
    /// This gracefully shuts down the transport, cleaning up resources.
    async fn close(&mut self) -> Result<(), TransportError>;

    /// Check if the transport is connected.
    fn is_connected(&self) -> bool;

    /// Get the current transport state.
    fn state(&self) -> TransportState;
}

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

    #[test]
    fn test_jsonrpc_request_new() {
        let req = JsonRpcRequest::new(1, "test_method", None);
        assert_eq!(req.jsonrpc, "2.0");
        assert_eq!(req.method, "test_method");
        assert_eq!(req.id, serde_json::Value::Number(1.into()));
        assert!(req.params.is_none());
    }

    #[test]
    fn test_jsonrpc_request_with_params() {
        let params = serde_json::json!({"key": "value"});
        let req = JsonRpcRequest::new(2, "test_method", Some(params.clone()));
        assert_eq!(req.params, Some(params));
    }

    #[test]
    fn test_jsonrpc_request_serialize() {
        let req = JsonRpcRequest::new(1, "test_method", None);
        let json = serde_json::to_string(&req).unwrap();
        assert!(json.contains("\"jsonrpc\":\"2.0\""));
        assert!(json.contains("\"method\":\"test_method\""));
        assert!(json.contains("\"id\":1"));
    }

    #[test]
    fn test_jsonrpc_notification() {
        let notif = JsonRpcNotification::new("test_notification", None);
        assert_eq!(notif.jsonrpc, "2.0");
        assert_eq!(notif.method, "test_notification");
        assert!(notif.params.is_none());
    }

    #[test]
    fn test_jsonrpc_error() {
        let error = JsonRpcError {
            code: -32600,
            message: "Invalid Request".to_string(),
            data: None,
        };
        assert_eq!(error.code, -32600);
        assert_eq!(error.message, "Invalid Request");
    }

    #[test]
    fn test_jsonrpc_result_success() {
        let response = JsonRpcResponse {
            jsonrpc: "2.0".to_string(),
            id: serde_json::Value::Number(1.into()),
            result: serde_json::json!({"status": "ok"}),
        };
        let result = JsonRpcResult::Success(response);

        assert!(result.is_success());
        assert!(!result.is_error());
        assert!(result.result().is_some());
        assert!(result.error().is_none());
    }

    #[test]
    fn test_jsonrpc_result_error() {
        let error_response = JsonRpcErrorResponse {
            jsonrpc: "2.0".to_string(),
            id: serde_json::Value::Number(1.into()),
            error: JsonRpcError {
                code: -32600,
                message: "Invalid Request".to_string(),
                data: None,
            },
        };
        let result = JsonRpcResult::Error(error_response);

        assert!(!result.is_success());
        assert!(result.is_error());
        assert!(result.result().is_none());
        assert!(result.error().is_some());
    }
}
