//! HTTP request handlers for AI service API

use std::sync::Arc;

use axum::{
    extract::State,
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
    Json, Router,
};
use ontoref_derive::onto_api;
use serde_json::json;
use tracing::debug;

use crate::service::{
    AiService, AskRequest, AskResponse, BestPractice, DagResponse, McpToolRequest, McpToolResponse,
};

/// Create AI service HTTP routes
pub fn create_routes(state: Arc<AiService>) -> Router {
    Router::new()
        .route("/api/v1/ai/mcp/tool", post(call_mcp_tool_handler))
        .route("/api/v1/ai/ask", post(ask_handler))
        .route("/api/v1/ai/dag/extensions", get(get_extension_dag_handler))
        .route(
            "/api/v1/ai/knowledge/best-practices",
            get(get_best_practices_handler),
        )
        .route("/health", get(health_check_handler))
        .route("/api/catalog", get(crate::api_catalog::api_catalog))
        .with_state(state)
}

#[onto_api(
    method = "POST",
    path = "/api/v1/ai/mcp/tool",
    description = "Invoke an MCP tool by name with arguments",
    auth = "bearer",
    actors = "developer, agent",
    tags = "ai, mcp",
    feature = ""
)]
async fn call_mcp_tool_handler(
    State(service): State<Arc<AiService>>,
    Json(req): Json<McpToolRequest>,
) -> Result<Json<McpToolResponse>, McpToolError> {
    debug!("Calling MCP tool: {}", req.tool_name);

    let response = service
        .call_mcp_tool(req)
        .await
        .map_err(McpToolError::from)?;

    Ok(Json(response))
}

#[onto_api(
    method = "POST",
    path = "/api/v1/ai/ask",
    description = "Ask a RAG-powered question grounded in Nickel schemas and deployment history",
    auth = "bearer",
    actors = "developer, agent",
    tags = "ai, rag",
    feature = ""
)]
async fn ask_handler(
    State(service): State<Arc<AiService>>,
    Json(req): Json<AskRequest>,
) -> Result<Json<AskResponse>, AskError> {
    debug!("Processing RAG question: {}", req.question);

    let response = service.ask(req).await.map_err(AskError::from)?;

    Ok(Json(response))
}

#[onto_api(
    method = "GET",
    path = "/api/v1/ai/dag/extensions",
    description = "Get the extension dependency DAG used by AI for schema-aware config generation",
    auth = "bearer",
    actors = "developer, agent",
    tags = "ai, dag, extensions",
    feature = ""
)]
async fn get_extension_dag_handler(
    State(service): State<Arc<AiService>>,
) -> Result<Json<DagResponse>, InternalError> {
    debug!("Getting extension DAG");

    let dag = service
        .get_extension_dag()
        .await
        .map_err(|e| InternalError(e.to_string()))?;

    Ok(Json(dag))
}

#[onto_api(
    method = "GET",
    path = "/api/v1/ai/knowledge/best-practices",
    description = "Get best practices for a given category from the knowledge base",
    auth = "bearer",
    actors = "developer, agent",
    tags = "ai, knowledge",
    feature = ""
)]
async fn get_best_practices_handler(
    State(service): State<Arc<AiService>>,
    axum::extract::Query(params): axum::extract::Query<std::collections::HashMap<String, String>>,
) -> Result<Json<Vec<BestPractice>>, InternalError> {
    let category = params
        .get("category")
        .map(|s| s.as_str())
        .unwrap_or("general");

    debug!("Getting best practices for category: {}", category);

    let practices = service
        .get_best_practices(category)
        .await
        .map_err(|e| InternalError(e.to_string()))?;

    Ok(Json(practices))
}

#[onto_api(
    method = "GET",
    path = "/health",
    description = "AI service health check",
    auth = "none",
    actors = "developer, agent, ci",
    tags = "health",
    feature = ""
)]
async fn health_check_handler(
    State(service): State<Arc<AiService>>,
) -> Result<StatusCode, InternalError> {
    service
        .health_check()
        .await
        .map_err(|e| InternalError(e.to_string()))?;

    Ok(StatusCode::OK)
}

// Error types for handlers

/// MCP tool error
enum McpToolError {
    Internal(String),
}

impl From<anyhow::Error> for McpToolError {
    fn from(err: anyhow::Error) -> Self {
        McpToolError::Internal(err.to_string())
    }
}

impl IntoResponse for McpToolError {
    fn into_response(self) -> axum::response::Response {
        match self {
            McpToolError::Internal(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(json!({
                    "error": format!("MCP tool execution failed: {}", err)
                })),
            )
                .into_response(),
        }
    }
}

/// Ask/RAG error
enum AskError {
    Internal(String),
}

impl From<anyhow::Error> for AskError {
    fn from(err: anyhow::Error) -> Self {
        AskError::Internal(err.to_string())
    }
}

impl IntoResponse for AskError {
    fn into_response(self) -> axum::response::Response {
        match self {
            AskError::Internal(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(json!({
                    "error": format!("Question answering failed: {}", err)
                })),
            )
                .into_response(),
        }
    }
}

/// Internal server error
struct InternalError(String);

impl IntoResponse for InternalError {
    fn into_response(self) -> axum::response::Response {
        (
            StatusCode::INTERNAL_SERVER_ERROR,
            Json(json!({
                "error": self.0
            })),
        )
            .into_response()
    }
}
