use crate::converter::{convert_prompt_to_graph, ConvertRequest};
use crate::graph_ops::{export_to_dot, export_to_json, query_nodes, validate_graph_payload, ValidationReport};
use agileplus_domain::intent_graph::{IntentGraph, NodeType, Status};
use agileplus_sqlite::Storage;
use anyhow::Result;
use axum::{
    extract::{Json, Path, State},
    response::{IntoResponse, Json as AxumJson, Sse},
    routing::{get, post},
    Router,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::convert::Infallible;
use std::net::SocketAddr;
use tokio::net::TcpListener;

/// HTTP API server state: wraps the shared SQLite storage.
#[derive(Clone, Debug)]
pub struct AppState(pub Storage);

/// Start the HTTP server on the given address, backed by SQLite at `db_path`.
pub async fn serve(addr: SocketAddr, db_path: &str) -> Result<()> {
    let storage = Storage::open(db_path)?;
    storage.migrate()?;
    let state = AppState(storage);
    let app = router(state);
    let listener = TcpListener::bind(addr).await?;
    axum::serve(listener, app).await?;
    Ok(())
}

/// Build the axum router with shared application state.
pub fn router(state: AppState) -> Router {
    Router::new()
        .route("/health", get(health))
        .route("/convert", post(convert_handler))
        .route("/v1/convert", post(convert_handler))
        .route("/v1/graphs", post(create_graph_handler))
        .route("/v1/graphs/:id", get(get_graph_handler))
        .route("/v1/graphs/:id/validate", post(validate_graph_handler))
        .route("/v1/graphs/query", post(query_graph_handler))
        .route("/v1/graphs/:id/export", post(export_graph_handler))
        .route("/v1/graphs/:id/stream", get(stream_graph_handler))
        .with_state(state)
}

async fn health() -> impl IntoResponse {
    AxumJson(HealthResponse {
        status: "ok".to_string(),
        service: "agileplus-mcp-intent".to_string(),
    })
}

#[derive(Serialize, Deserialize)]
struct HealthResponse {
    status: String,
    service: String,
}

async fn convert_handler(Json(req): Json<ConvertRequest>) -> impl IntoResponse {
    match convert_prompt_to_graph(&req) {
        Ok(resp) => AxumJson(resp).into_response(),
        Err(e) => {
            let status = axum::http::StatusCode::UNPROCESSABLE_ENTITY;
            let body = AxumJson(ErrorResponse { error: e.to_string() });
            (status, body).into_response()
        }
    }
}

/// Store a new graph (provided as JSON body with an optional `id` field).
async fn create_graph_handler(
    State(state): State<AppState>,
    Json(body): Json<Value>,
) -> impl IntoResponse {
    let id = body
        .get("id")
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .unwrap_or_else(|| format!("graph-{}", ulid::Ulid::new()));
    let graph: IntentGraph = match serde_json::from_value(body.clone()) {
        Ok(g) => g,
        Err(e) => {
            let status = axum::http::StatusCode::UNPROCESSABLE_ENTITY;
            let body = AxumJson(ErrorResponse {
                error: format!("invalid graph payload: {e}"),
            });
            return (status, body).into_response();
        }
    };
    match state.0.save_graph(&id, &graph) {
        Ok(()) => {
            let status = axum::http::StatusCode::CREATED;
            let body = AxumJson(serde_json::json!({"id": id, "status": "created"}));
            (status, body).into_response()
        }
        Err(e) => {
            let status = axum::http::StatusCode::INTERNAL_SERVER_ERROR;
            let body = AxumJson(ErrorResponse {
                error: format!("storage error: {e}"),
            });
            (status, body).into_response()
        }
    }
}

/// Retrieve a stored graph by ID.
async fn get_graph_handler(
    Path(id): Path<String>,
    State(state): State<AppState>,
) -> impl IntoResponse {
    match state.0.load_graph(&id) {
        Ok(Some(graph)) => AxumJson(graph).into_response(),
        Ok(None) => {
            let status = axum::http::StatusCode::NOT_FOUND;
            let body = AxumJson(ErrorResponse {
                error: format!("graph not found: {id}"),
            });
            (status, body).into_response()
        }
        Err(e) => {
            let status = axum::http::StatusCode::INTERNAL_SERVER_ERROR;
            let body = AxumJson(ErrorResponse {
                error: format!("storage error: {e}"),
            });
            (status, body).into_response()
        }
    }
}

// ---------------------------------------------------------------------------
// Validate endpoint
// ---------------------------------------------------------------------------

#[derive(Serialize, Deserialize)]
struct ValidateGraphRequest {
    pub graph: IntentGraph,
}

#[derive(Serialize, Deserialize)]
struct ValidateGraphResponse {
    pub report: ValidationReport,
}

async fn validate_graph_handler(Json(req): Json<ValidateGraphRequest>) -> impl IntoResponse {
    match validate_graph_payload(&req.graph) {
        Ok(report) => AxumJson(ValidateGraphResponse { report }).into_response(),
        Err(e) => {
            let status = axum::http::StatusCode::UNPROCESSABLE_ENTITY;
            let body = AxumJson(ErrorResponse { error: e.to_string() });
            (status, body).into_response()
        }
    }
}

// ---------------------------------------------------------------------------
// Query endpoint
// ---------------------------------------------------------------------------

#[derive(Serialize, Deserialize)]
struct QueryGraphRequest {
    pub graph: IntentGraph,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub node_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub status: Option<String>,
}

#[derive(Serialize, Deserialize)]
struct QueryGraphResponse {
    pub nodes: Vec<agileplus_domain::intent_graph::Node>,
    pub total: usize,
}

async fn query_graph_handler(Json(req): Json<QueryGraphRequest>) -> impl IntoResponse {
    let node_type = req.node_type.as_ref().and_then(|s| s.parse().ok());
    let status = req.status.as_ref().and_then(|s| s.parse().ok());
    let nodes: Vec<agileplus_domain::intent_graph::Node> =
        query_nodes(&req.graph, node_type, status)
            .into_iter()
            .cloned()
            .collect();
    let total = nodes.len();
    AxumJson(QueryGraphResponse { nodes, total }).into_response()
}

// ---------------------------------------------------------------------------
// Export endpoint
// ---------------------------------------------------------------------------

#[derive(Serialize, Deserialize)]
struct ExportGraphRequest {
    pub graph: IntentGraph,
    pub format: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pretty: Option<bool>,
}

#[derive(Serialize, Deserialize)]
struct ExportGraphResponse {
    pub data: String,
    pub format: String,
}

async fn export_graph_handler(Json(req): Json<ExportGraphRequest>) -> impl IntoResponse {
    let format = req.format.to_lowercase();
    let result = match format.as_str() {
        "dot" => Ok(export_to_dot(&req.graph)),
        "json" => {
            let pretty = req.pretty.unwrap_or(true);
            export_to_json(&req.graph, pretty)
        }
        _ => Err(anyhow::anyhow!("Unsupported format: {}", req.format)),
    };
    match result {
        Ok(data) => AxumJson(ExportGraphResponse { data, format }).into_response(),
        Err(e) => {
            let status = axum::http::StatusCode::BAD_REQUEST;
            let body = AxumJson(ErrorResponse { error: e.to_string() });
            (status, body).into_response()
        }
    }
}

// ---------------------------------------------------------------------------
// SSE stream endpoint
// ---------------------------------------------------------------------------

async fn stream_graph_handler(Path(id): Path<String>) -> impl IntoResponse {
    let event = axum::response::sse::Event::default()
        .data(format!("{{\"graph_id\":\"{}\",\"event\":\"connected\"}}", id));
    let stream = tokio_stream::iter(vec![Ok::<_, Infallible>(event)]);
    Sse::new(stream)
}

// ---------------------------------------------------------------------------
// Error response
// ---------------------------------------------------------------------------

#[derive(Serialize, Deserialize)]
struct ErrorResponse {
    error: String,
}

// ---------------------------------------------------------------------------
// MCP Tool Interface
// ---------------------------------------------------------------------------

/// MCP Tool request wrapper.
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct McpToolRequest {
    pub name: String,
    pub arguments: Value,
}

/// MCP Tool response wrapper.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct McpToolResponse {
    pub content: Vec<McpContentItem>,
    pub is_error: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct McpContentItem {
    #[serde(rename = "type")]
    pub content_type: String,
    pub text: String,
}

/// Execute an MCP tool by name, with storage for persistence.
pub fn mcp_tool_execute(storage: &Storage, req: McpToolRequest) -> McpToolResponse {
    match req.name.as_str() {
        "convert_prompt_to_intent_graph" => mcp_tool_convert(req.arguments),
        "validate_intent_graph" => mcp_tool_validate(req.arguments),
        "export_intent_graph" => mcp_tool_export(req.arguments),
        "query_intent_graph" => mcp_tool_query(req.arguments),
        "save_intent_graph" => mcp_tool_save(storage, req.arguments),
        "get_intent_graph" => mcp_tool_get(storage, req.arguments),
        _ => mcp_error_response(format!("Unknown tool: {}", req.name)),
    }
}

fn mcp_tool_save(storage: &Storage, args: Value) -> McpToolResponse {
    let id = args
        .get("id")
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
        .unwrap_or_else(|| format!("graph-{}", ulid::Ulid::new()));
    let graph: IntentGraph = match args.get("graph").and_then(|v| serde_json::from_value(v.clone()).ok()) {
        Some(g) => g,
        None => return mcp_error_response("Missing 'graph' argument".to_string()),
    };
    match storage.save_graph(&id, &graph) {
        Ok(()) => mcp_json_response(&serde_json::json!({"id": id, "status": "saved"})),
        Err(e) => mcp_error_response(format!("Save error: {e}")),
    }
}

fn mcp_tool_get(storage: &Storage, args: Value) -> McpToolResponse {
    let id = match args.get("id").and_then(|v| v.as_str()) {
        Some(s) => s,
        None => return mcp_error_response("Missing 'id' argument".to_string()),
    };
    match storage.load_graph(id) {
        Ok(Some(graph)) => mcp_json_response(&graph),
        Ok(None) => mcp_error_response(format!("Graph not found: {id}")),
        Err(e) => mcp_error_response(format!("Storage error: {e}")),
    }
}

fn mcp_tool_convert(args: Value) -> McpToolResponse {
    let req: ConvertRequest = match serde_json::from_value(args) {
        Ok(r) => r,
        Err(e) => return mcp_error_response(format!("Invalid arguments: {}", e)),
    };
    match convert_prompt_to_graph(&req) {
        Ok(resp) => mcp_json_response(&resp),
        Err(e) => mcp_error_response(format!("Conversion error: {}", e)),
    }
}

fn mcp_tool_validate(args: Value) -> McpToolResponse {
    let graph: IntentGraph = match serde_json::from_value(args) {
        Ok(g) => g,
        Err(e) => return mcp_error_response(format!("Invalid graph: {}", e)),
    };
    match validate_graph_payload(&graph) {
        Ok(report) => mcp_json_response(&report),
        Err(e) => mcp_error_response(format!("Validation error: {}", e)),
    }
}

fn mcp_tool_export(args: Value) -> McpToolResponse {
    let graph: IntentGraph = match args.get("graph").and_then(|v| serde_json::from_value(v.clone()).ok()) {
        Some(g) => g,
        None => return mcp_error_response("Missing 'graph' argument".to_string()),
    };
    let format = args.get("format").and_then(|v| v.as_str()).unwrap_or("json");
    let pretty = args.get("pretty").and_then(|v| v.as_bool()).unwrap_or(true);
    let result = match format {
        "dot" => Ok(export_to_dot(&graph)),
        "json" => export_to_json(&graph, pretty),
        _ => Err(anyhow::anyhow!("Unsupported format: {}", format)),
    };
    match result {
        Ok(data) => {
            let resp = ExportGraphResponse { data, format: format.to_string() };
            mcp_json_response(&resp)
        }
        Err(e) => mcp_error_response(format!("Export error: {}", e)),
    }
}

fn mcp_tool_query(args: Value) -> McpToolResponse {
    let graph: IntentGraph = match args.get("graph").and_then(|v| serde_json::from_value(v.clone()).ok()) {
        Some(g) => g,
        None => return mcp_error_response("Missing 'graph' argument".to_string()),
    };
    let node_type = args.get("node_type").and_then(|v| v.as_str()).and_then(|s| s.parse::<NodeType>().ok());
    let status = args.get("status").and_then(|v| v.as_str()).and_then(|s| s.parse::<Status>().ok());
    let nodes: Vec<agileplus_domain::intent_graph::Node> =
        query_nodes(&graph, node_type, status)
            .into_iter()
            .cloned()
            .collect();
    let total = nodes.len();
    mcp_json_response(&QueryGraphResponse { nodes, total })
}

pub(crate) fn mcp_json_response<T: Serialize>(data: &T) -> McpToolResponse {
    let text = match serde_json::to_string_pretty(data) {
        Ok(s) => s,
        Err(e) => return mcp_error_response(format!("Serialization error: {}", e)),
    };
    McpToolResponse {
        content: vec![McpContentItem { content_type: "text".to_string(), text }],
        is_error: false,
    }
}

pub(crate) fn mcp_error_response(msg: String) -> McpToolResponse {
    McpToolResponse {
        content: vec![McpContentItem { content_type: "text".to_string(), text: msg }],
        is_error: true,
    }
}
