//! TypeAgent HTTP Server
//!
//! Provides HTTP API for agent execution

use axum::{
    extract::{Path, State},
    http::{header, Method, StatusCode},
    response::{IntoResponse, Response},
    routing::{get, post},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tower_http::cors::{Any, CorsLayer};
use typedialog_ag_core::{AgentLoader, ExecutionResult};

/// Server configuration
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ServerConfig {
    pub port: u16,
    pub host: String,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            port: 8765,
            host: "127.0.0.1".to_string(),
        }
    }
}

impl ServerConfig {
    /// Load configuration from file or use defaults
    ///
    /// If config_path is provided, loads from that file.
    /// Otherwise, searches in standard locations:
    /// - ~/.config/typedialog/ag/{TYPEDIALOG_ENV}.toml
    /// - ~/.config/typedialog/ag/config.toml
    /// - Falls back to defaults
    pub fn load_with_cli(config_path: Option<&std::path::Path>) -> anyhow::Result<Self> {
        if let Some(path) = config_path {
            // Load from explicit path
            let content = std::fs::read_to_string(path).map_err(|e| {
                anyhow::anyhow!("Failed to read config file {}: {}", path.display(), e)
            })?;
            let config: ServerConfig = toml::from_str(&content).map_err(|e| {
                anyhow::anyhow!("Failed to parse config file {}: {}", path.display(), e)
            })?;
            return Ok(config);
        }

        // Try standard locations
        let env = std::env::var("TYPEDIALOG_ENV").unwrap_or_else(|_| "default".to_string());
        let config_dir = dirs::config_dir()
            .map(|p| p.join("typedialog").join("ag"))
            .unwrap_or_else(|| std::path::PathBuf::from(".config/typedialog/ag"));

        let search_paths = vec![
            config_dir.join(format!("{}.toml", env)),
            config_dir.join("config.toml"),
        ];

        for path in search_paths {
            if path.exists() {
                let content = std::fs::read_to_string(&path).map_err(|e| {
                    anyhow::anyhow!("Failed to read config file {}: {}", path.display(), e)
                })?;
                let config: ServerConfig = toml::from_str(&content).map_err(|e| {
                    anyhow::anyhow!("Failed to parse config file {}: {}", path.display(), e)
                })?;
                return Ok(config);
            }
        }

        // Fall back to defaults
        Ok(Self::default())
    }
}

/// Server state
#[derive(Clone)]
pub struct AppState {
    loader: Arc<AgentLoader>,
}

impl AppState {
    pub fn new() -> Self {
        Self {
            loader: Arc::new(AgentLoader::new()),
        }
    }
}

impl Default for AppState {
    fn default() -> Self {
        Self::new()
    }
}

/// Execute agent request
#[derive(Debug, Deserialize)]
pub struct ExecuteRequest {
    /// Path to agent file
    pub agent_file: String,
    /// Input variables
    #[serde(default)]
    pub inputs: HashMap<String, serde_json::Value>,
}

/// Execute agent response
#[derive(Debug, Serialize)]
pub struct ExecuteResponse {
    /// Generated output
    pub output: String,
    /// Whether validation passed
    pub validation_passed: bool,
    /// Validation errors if any
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub validation_errors: Vec<String>,
    /// Metadata
    pub metadata: ExecutionMetadata,
}

/// Execution metadata
#[derive(Debug, Serialize)]
pub struct ExecutionMetadata {
    /// Duration in milliseconds
    pub duration_ms: Option<u64>,
    /// Token count
    pub tokens: Option<usize>,
    /// Model used
    pub model: Option<String>,
}

impl From<ExecutionResult> for ExecuteResponse {
    fn from(result: ExecutionResult) -> Self {
        Self {
            output: result.output,
            validation_passed: result.validation_passed,
            validation_errors: result.validation_errors,
            metadata: ExecutionMetadata {
                duration_ms: result.metadata.duration_ms,
                tokens: result.metadata.tokens,
                model: result.metadata.model,
            },
        }
    }
}

/// Transpile request
#[derive(Debug, Deserialize)]
pub struct TranspileRequest {
    /// Agent MDX content or file path
    pub content: String,
}

/// Transpile response
#[derive(Debug, Serialize)]
pub struct TranspileResponse {
    /// Transpiled Nickel code
    pub nickel_code: String,
}

/// Validate request
#[derive(Debug, Deserialize)]
pub struct ValidateRequest {
    /// Path to agent file
    pub agent_file: String,
}

/// Validate response
#[derive(Debug, Serialize)]
pub struct ValidateResponse {
    /// Whether validation passed
    pub valid: bool,
    /// Agent configuration
    pub config: Option<AgentConfigResponse>,
    /// Validation errors if any
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub errors: Vec<String>,
}

/// Agent config response
#[derive(Debug, Serialize)]
pub struct AgentConfigResponse {
    pub role: String,
    pub llm: String,
    pub max_tokens: usize,
    pub temperature: f64,
}

/// Error response
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
    pub error: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub details: Option<String>,
}

/// API Error wrapper
pub struct ApiError {
    status: StatusCode,
    message: String,
    details: Option<String>,
}

impl ApiError {
    fn new(status: StatusCode, message: impl Into<String>) -> Self {
        Self {
            status,
            message: message.into(),
            details: None,
        }
    }

    fn with_details(mut self, details: impl Into<String>) -> Self {
        self.details = Some(details.into());
        self
    }
}

impl IntoResponse for ApiError {
    fn into_response(self) -> Response {
        let body = Json(ErrorResponse {
            error: self.message,
            details: self.details,
        });
        (self.status, body).into_response()
    }
}

impl From<typedialog_ag_core::Error> for ApiError {
    fn from(err: typedialog_ag_core::Error) -> Self {
        let status = if err.is_parse() || err.is_transpile() || err.is_nickel_eval() {
            StatusCode::BAD_REQUEST
        } else if err.is_validation() {
            StatusCode::UNPROCESSABLE_ENTITY
        } else {
            StatusCode::INTERNAL_SERVER_ERROR
        };

        ApiError::new(status, "Agent execution failed").with_details(err.to_string())
    }
}

/// Create application router
pub fn app() -> Router {
    let state = AppState::new();

    // Configure CORS
    let cors = CorsLayer::new()
        .allow_origin(Any)
        .allow_methods([Method::GET, Method::POST])
        .allow_headers([header::CONTENT_TYPE]);

    Router::new()
        .route("/health", get(health))
        .route("/execute", post(execute))
        .route("/transpile", post(transpile))
        .route("/validate", post(validate))
        .route("/agents/{name}/execute", post(execute_by_name))
        .layer(cors)
        .with_state(state)
}

/// Health check endpoint
async fn health() -> &'static str {
    "OK"
}

/// Execute agent endpoint
async fn execute(
    State(state): State<AppState>,
    Json(req): Json<ExecuteRequest>,
) -> Result<Json<ExecuteResponse>, ApiError> {
    let agent_path = std::path::Path::new(&req.agent_file);

    // Load agent
    let agent = state
        .loader
        .load(agent_path)
        .await
        .map_err(ApiError::from)?;

    // Execute
    let result = state
        .loader
        .execute(&agent, req.inputs)
        .await
        .map_err(ApiError::from)?;

    Ok(Json(result.into()))
}

/// Execute agent by name (from standard location)
async fn execute_by_name(
    State(state): State<AppState>,
    Path(name): Path<String>,
    Json(inputs): Json<HashMap<String, serde_json::Value>>,
) -> Result<Json<ExecuteResponse>, ApiError> {
    // Assume agents are in ./agents/ directory
    let agent_file = format!("./agents/{}.agent.mdx", name);
    let agent_path = std::path::Path::new(&agent_file);

    if !agent_path.exists() {
        return Err(ApiError::new(
            StatusCode::NOT_FOUND,
            format!("Agent '{}' not found", name),
        ));
    }

    // Load agent
    let agent = state
        .loader
        .load(agent_path)
        .await
        .map_err(ApiError::from)?;

    // Execute
    let result = state
        .loader
        .execute(&agent, inputs)
        .await
        .map_err(ApiError::from)?;

    Ok(Json(result.into()))
}

/// Transpile agent endpoint
async fn transpile(
    State(_state): State<AppState>,
    Json(req): Json<TranspileRequest>,
) -> Result<Json<TranspileResponse>, ApiError> {
    // Parse content as MDX
    let parser = typedialog_ag_core::MarkupParser::new();
    let ast = parser.parse(&req.content).map_err(ApiError::from)?;

    // Transpile to Nickel
    let transpiler = typedialog_ag_core::NickelTranspiler::new();
    let nickel_code = transpiler.transpile(&ast).map_err(ApiError::from)?;

    Ok(Json(TranspileResponse { nickel_code }))
}

/// Validate agent endpoint
async fn validate(
    State(state): State<AppState>,
    Json(req): Json<ValidateRequest>,
) -> Result<Json<ValidateResponse>, ApiError> {
    let agent_path = std::path::Path::new(&req.agent_file);

    match state.loader.load(agent_path).await {
        Ok(agent) => Ok(Json(ValidateResponse {
            valid: true,
            config: Some(AgentConfigResponse {
                role: agent.config.role,
                llm: agent.config.llm,
                max_tokens: agent.config.max_tokens,
                temperature: agent.config.temperature,
            }),
            errors: vec![],
        })),
        Err(err) => Ok(Json(ValidateResponse {
            valid: false,
            config: None,
            errors: vec![err.to_string()],
        })),
    }
}

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

    #[test]
    fn test_server_config_default() {
        let config = ServerConfig::default();
        assert_eq!(config.port, 8765);
        assert_eq!(config.host, "127.0.0.1");
    }

    #[test]
    fn test_app_state_new() {
        let state = AppState::new();
        assert!(Arc::strong_count(&state.loader) == 1);
    }

    #[tokio::test]
    async fn test_health_endpoint() {
        let response = health().await;
        assert_eq!(response, "OK");
    }
}
