use crate::error::{NpcError, Result};
use crate::r#gen::Message;
use crate::npc_compiler::Team;
use axum::{
    Router,
    extract::{Json, Path as AxumPath, State},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post},
};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

pub struct ServerConfig {
    pub http_port: u16,
    pub mcp_enabled: bool,
    pub host: String,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            http_port: 5337,
            mcp_enabled: true,
            host: "0.0.0.0".to_string(),
        }
    }
}

pub struct ServerState {
    pub team: Team,
    pub active_npc_name: String,
    pub conversations: HashMap<String, Vec<Message>>,
}

type AppState = Arc<Mutex<ServerState>>;

fn json_response(value: serde_json::Value) -> impl IntoResponse {
    (StatusCode::OK, Json(value))
}

fn error_response(msg: &str) -> impl IntoResponse {
    (
        StatusCode::BAD_REQUEST,
        Json(serde_json::json!({"error": msg})),
    )
}

pub fn create_app(state: AppState) -> Router {
    Router::new()
        .route("/health", get(health))
        .route("/api/team", get(get_team))
        .route("/api/npcs", get(get_npcs))
        .route("/api/npc/switch", post(switch_npc))
        .route("/api/jinxes", get(get_jinxes))
        .route("/api/chat", post(chat))
        .route("/api/check_command", post(check_command))
        .route("/api/jinx", post(execute_jinx))
        .route("/api/models", get(get_models))
        .route("/api/conversations", get(list_conversations))
        .route(
            "/api/conversations",
            post(create_conversation).get(list_conversations),
        )
        .route("/api/search", post(search))
        .route("/api/image", post(generate_image))
        .route("/api/tts", post(tts))
        .route("/api/stt", post(stt))
        .route("/api/settings", get(get_settings))
        .route("/api/settings", post(save_settings).get(get_settings))
        .route("/api/kg/stats", get(kg_stats))
        .route("/api/kg/search", post(kg_search))
        .route("/api/kg/facts", get(get_kg_facts))
        .route("/api/kg/concepts", get(get_kg_concepts))
        .route("/api/kg/node", post(add_kg_node))
        .route(
            "/api/kg/node/:node_id",
            post(update_kg_node).delete(delete_kg_node),
        )
        .route("/api/kg/edge", post(add_kg_edge))
        .route("/api/kg/process", post(trigger_kg_process))
        .route("/api/kg/ingest", post(ingest_to_kg))
        .route("/api/kg/query", post(query_kg))
        .route("/api/jinx/save", post(save_jinx))
        .route("/api/jinx/delete", post(delete_jinx))
        .route("/api/jinx/test", post(test_jinx))
        .route("/api/attachments/:message_id", get(get_attachments))
        .route(
            "/api/settings/global",
            get(get_global_settings).post(save_global_settings),
        )
        .route(
            "/api/settings/project",
            get(get_project_settings).post(save_project_settings),
        )
        .route("/api/memories/extract", post(extract_memories))
        .route("/api/command/:command", post(api_command))
        .route("/api/capture", post(capture))
        .route("/api/memories/pending", get(get_pending_memories_route))
        .route("/api/memories/approve", post(approve_memories))
        .route("/api/memories/search", post(search_memories))
        .route(
            "/api/conversations/search",
            post(search_conversations_route),
        )
        .route(
            "/api/conversations/:id/messages",
            get(get_conversation_messages),
        )
        .route(
            "/api/conversations/:id/messages/:msg_id",
            axum::routing::delete(delete_message),
        )
        .route("/api/npc/save", post(save_npc))
        .route("/api/team/sync/status", get(team_sync_status))
        .route("/api/team/sync/init", post(team_sync_init))
        .route("/api/team/sync/pull", post(team_sync_pull))
        .route("/api/team/sync/commit", post(team_sync_commit))
        .route("/api/team/sync/diff", get(team_sync_diff))
        .route("/api/ollama/status", get(ollama_status))
        .route("/api/ollama/models", get(get_ollama_models))
        .route("/api/video", post(generate_video_api))
        .route("/api/npc/executions", get(get_npc_executions_route))
        .route("/api/jinx/executions", get(get_jinx_executions_route))
        .with_state(state)
}

async fn health() -> impl IntoResponse {
    Json(serde_json::json!({"status": "ok"}))
}

async fn get_team(State(state): State<AppState>) -> impl IntoResponse {
    let state = state.lock().await;
    Json(serde_json::json!({
        "npcs": state.team.npc_names(),
        "jinxes": state.team.jinx_names(),
        "active_npc": state.active_npc_name,
        "context": state.team.context,
    }))
}

async fn get_npcs(State(state): State<AppState>) -> impl IntoResponse {
    let state = state.lock().await;
    let npcs: Vec<serde_json::Value> = state.team.npcs.values().map(|n| n.to_dict()).collect();
    Json(serde_json::json!({"npcs": npcs}))
}

async fn switch_npc(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let name = body["npc"].as_str().unwrap_or("");
    let mut state = state.lock().await;
    if state.team.get_npc(name).is_some() {
        state.active_npc_name = name.to_string();
        Json(serde_json::json!({"status": "switched", "npc": name}))
    } else {
        Json(serde_json::json!({"error": format!("NPC '{}' not found", name)}))
    }
}

async fn get_jinxes(State(state): State<AppState>) -> impl IntoResponse {
    let state = state.lock().await;
    let jinxes: Vec<serde_json::Value> = state.team.jinxes.values().map(|j| j.to_dict()).collect();
    Json(serde_json::json!({"jinxes": jinxes}))
}

async fn chat(
    State(state_arc): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let input = body["message"].as_str().unwrap_or("").to_string();
    let npc_name = body["npc"].as_str().map(String::from);
    let conversation_id = body["conversation_id"].as_str().map(String::from);

    let (npc, prev_messages, conv_id, team_context) = {
        let state = state_arc.lock().await;
        let name = npc_name.as_deref().unwrap_or(&state.active_npc_name);
        let npc = state.team.get_npc(name).cloned();
        let conv_id = conversation_id.unwrap_or_else(|| "default".to_string());
        let prev = state
            .conversations
            .get(&conv_id)
            .cloned()
            .unwrap_or_default();
        let ctx = state.team.context.clone();
        (npc, prev, conv_id, ctx)
    };

    if let Some(npc) = npc {
        let system = npc.system_prompt(team_context.as_deref());
        let mut messages = vec![Message::system(system)];
        messages.extend(prev_messages);
        messages.push(Message::user(&input));

        match crate::r#gen::get_genai_response(
            &npc.resolved_provider(),
            &npc.resolved_model(),
            &messages,
            None,
            npc.api_url.as_deref(),
            None,
            None,
            false,
            None,
        )
        .await
        {
            Ok(resp) => {
                let output = resp.message.content.clone().unwrap_or_default();
                let mut state = state_arc.lock().await;
                let existing = state.conversations.entry(conv_id.clone()).or_default();
                existing.push(Message::user(&input));
                existing.push(Message {
                    role: "assistant".into(),
                    content: Some(output.clone()),
                    tool_calls: None,
                    tool_call_id: None,
                    name: None,
                });
                let usage = resp.usage.as_ref().map(|u| serde_json::json!({"input_tokens": u.prompt_tokens, "output_tokens": u.completion_tokens}));
                Json(
                    serde_json::json!({"response": output, "conversation_id": conv_id, "usage": usage}),
                )
            }
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        }
    } else {
        Json(serde_json::json!({"error": "NPC not found"}))
    }
}

async fn check_command(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let command = body["command"].as_str().unwrap_or("").to_string();
    let (npc, jinxes) = {
        let s = state.lock().await;
        (
            s.team.get_npc(&s.active_npc_name).cloned(),
            s.team.jinxes.clone(),
        )
    };

    if let Some(npc) = npc {
        let model = npc.resolved_model();
        let provider = npc.resolved_provider();
        match crate::llm_funcs::get_llm_response(
            &command,
            Some(&npc),
            Some(&model),
            Some(&provider),
            None,
            &[],
            None,
        )
        .await
        {
            Ok(result) => Json(
                serde_json::json!({"output": result.response, "model": result.model, "provider": result.provider}),
            ),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        }
    } else {
        Json(serde_json::json!({"error": "No active NPC"}))
    }
}

async fn execute_jinx(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let jinx_name = body["jinx"].as_str().unwrap_or("");
    let args: HashMap<String, String> = body["args"]
        .as_object()
        .map(|obj| {
            obj.iter()
                .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
                .collect()
        })
        .unwrap_or_default();

    let state = state.lock().await;
    if let Some(j) = state.team.jinxes.get(jinx_name) {
        let result = j.execute(&args);
        Json(serde_json::json!({"output": result.output, "success": result.success}))
    } else {
        Json(serde_json::json!({"error": format!("Jinx '{}' not found", jinx_name)}))
    }
}

async fn get_models() -> impl IntoResponse {
    let mut models: HashMap<&str, Vec<&str>> = HashMap::new();
    if std::env::var("OPENAI_API_KEY").is_ok() {
        models.insert("openai", vec!["gpt-4o", "gpt-4o-mini"]);
    }
    if std::env::var("ANTHROPIC_API_KEY").is_ok() {
        models.insert("anthropic", vec!["claude-sonnet-4", "claude-haiku-4"]);
    }
    if std::env::var("GEMINI_API_KEY").is_ok() || std::env::var("GOOGLE_API_KEY").is_ok() {
        models.insert("gemini", vec!["gemini-2.5-flash", "gemini-2.5-pro"]);
    }
    models.insert("ollama", vec!["llama3.2", "qwen3.5:2b"]);
    Json(serde_json::json!({"models": models}))
}

async fn list_conversations(State(state): State<AppState>) -> impl IntoResponse {
    let state = state.lock().await;
    let ids: Vec<&str> = state.conversations.keys().map(|s| s.as_str()).collect();
    Json(serde_json::json!({"conversations": ids}))
}

async fn create_conversation(State(state): State<AppState>) -> impl IntoResponse {
    let conv_id = crate::memory::start_new_conversation();
    let mut state = state.lock().await;
    state.conversations.insert(conv_id.clone(), Vec::new());
    Json(serde_json::json!({"conversation_id": conv_id}))
}

async fn search(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let query = body["query"].as_str().unwrap_or("");
    let provider = body["provider"].as_str().unwrap_or("duckduckgo");
    let num = body["num_results"].as_u64().unwrap_or(5) as usize;
    match crate::data::web::search_web(query, num, provider, None).await {
        Ok(results) => {
            let items: Vec<serde_json::Value> = results
                .iter()
                .map(|r| serde_json::json!({"title": r.title, "url": r.url, "snippet": r.snippet}))
                .collect();
            Json(serde_json::json!({"results": items}))
        }
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn generate_image(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let prompt = body["prompt"].as_str().unwrap_or("");
    let model = body["model"].as_str().unwrap_or("dall-e-3");
    let provider = body["provider"].as_str().unwrap_or("openai");
    match crate::llm_funcs::gen_image(prompt, Some(model), Some(provider), None, 1024, 1024, None)
        .await
    {
        Ok(img) => {
            use base64::Engine;
            let b64 = base64::engine::general_purpose::STANDARD.encode(&img.data);
            Json(serde_json::json!({"image": b64, "format": img.format}))
        }
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn tts(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let text = body["text"].as_str().unwrap_or("");
    let engine = body["engine"].as_str().unwrap_or("openai");
    let voice = body["voice"].as_str();
    match crate::r#gen::audio_gen::text_to_speech(text, engine, voice, None).await {
        Ok(audio) => {
            use base64::Engine;
            let b64 = base64::engine::general_purpose::STANDARD.encode(&audio);
            Json(serde_json::json!({"audio": b64}))
        }
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn stt(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let engine = body["engine"].as_str().unwrap_or("openai");
    if let Some(b64) = body["audio"].as_str() {
        use base64::Engine;
        match base64::engine::general_purpose::STANDARD.decode(b64) {
            Ok(data) => match crate::data::audio::speech_to_text(&data, engine, None).await {
                Ok(result) => Json(serde_json::json!(result)),
                Err(e) => Json(serde_json::json!({"error": e.to_string()})),
            },
            Err(e) => Json(serde_json::json!({"error": format!("Base64: {}", e)})),
        }
    } else {
        Json(serde_json::json!({"error": "Missing audio"}))
    }
}

async fn get_settings(State(state): State<AppState>) -> impl IntoResponse {
    let state = state.lock().await;
    Json(serde_json::json!({
        "active_npc": state.active_npc_name,
        "model": state.team.model,
        "provider": state.team.provider,
    }))
}

async fn save_settings(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let mut state = state.lock().await;
    if let Some(npc) = body["active_npc"].as_str() {
        state.active_npc_name = npc.to_string();
    }
    if let Some(model) = body["model"].as_str() {
        state.team.model = Some(model.to_string());
    }
    if let Some(provider) = body["provider"].as_str() {
        state.team.provider = Some(provider.to_string());
    }
    Json(serde_json::json!({"status": "updated"}))
}

async fn kg_stats() -> impl IntoResponse {
    Json(serde_json::json!({"status": "ok"}))
}

async fn kg_search(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let _query = body["query"].as_str().unwrap_or("");
    Json(serde_json::json!({"results": []}))
}

async fn capture() -> impl IntoResponse {
    match crate::data::image::capture_screenshot(true) {
        Ok(result) => Json(serde_json::json!(result)),
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn get_kg_facts() -> impl IntoResponse {
    Json(serde_json::json!({"facts": []}))
}

async fn get_kg_concepts() -> impl IntoResponse {
    Json(serde_json::json!({"concepts": []}))
}

async fn add_kg_node(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let name = body["name"].as_str().unwrap_or("");
    let node_type = body["type"].as_str().unwrap_or("Entity");
    let content = body["content"].as_str().unwrap_or("");
    Json(
        serde_json::json!({"status": "added", "name": name, "type": node_type, "content": content}),
    )
}

async fn update_kg_node(
    AxumPath(node_id): AxumPath<String>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    Json(serde_json::json!({"status": "updated", "node_id": node_id}))
}

async fn delete_kg_node(AxumPath(node_id): AxumPath<String>) -> impl IntoResponse {
    Json(serde_json::json!({"status": "deleted", "node_id": node_id}))
}

async fn add_kg_edge(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let source = body["source"].as_str().unwrap_or("");
    let target = body["target"].as_str().unwrap_or("");
    let relation = body["relation"].as_str().unwrap_or("related_to");
    Json(
        serde_json::json!({"status": "added", "source": source, "target": target, "relation": relation}),
    )
}

async fn trigger_kg_process(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let process_type = body["process"].as_str().unwrap_or("sleep");
    Json(serde_json::json!({"status": "triggered", "process": process_type}))
}

async fn ingest_to_kg(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let text = body["text"].as_str().unwrap_or("");
    Json(serde_json::json!({"status": "ingested", "text_length": text.len()}))
}

async fn query_kg(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let query = body["query"].as_str().unwrap_or("");
    let mode = body["mode"].as_str().unwrap_or("keyword");
    Json(serde_json::json!({"query": query, "mode": mode, "results": []}))
}

async fn save_jinx(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let name = body["name"].as_str().unwrap_or("");
    let description = body["description"].as_str().unwrap_or("");
    let state = state.lock().await;
    if let Some(dir) = &state.team.source_dir {
        let jinx_dir = std::path::Path::new(dir).join("jinxes");
        let _ = std::fs::create_dir_all(&jinx_dir);
        let path = jinx_dir.join(format!("{}.jinx", name));
        let content = serde_yaml::to_string(&body).unwrap_or_default();
        match std::fs::write(&path, content) {
            Ok(_) => Json(serde_json::json!({"status": "saved", "path": path.to_string_lossy()})),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        }
    } else {
        Json(serde_json::json!({"error": "No team source directory"}))
    }
}

async fn delete_jinx(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let name = body["name"].as_str().unwrap_or("");
    let state = state.lock().await;
    if let Some(dir) = &state.team.source_dir {
        let path = std::path::Path::new(dir)
            .join("jinxes")
            .join(format!("{}.jinx", name));
        match std::fs::remove_file(&path) {
            Ok(_) => Json(serde_json::json!({"status": "deleted"})),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        }
    } else {
        Json(serde_json::json!({"error": "No team source directory"}))
    }
}

async fn test_jinx(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let jinx_name = body["jinx"].as_str().unwrap_or("");
    let args: HashMap<String, String> = body["args"]
        .as_object()
        .map(|obj| {
            obj.iter()
                .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
                .collect()
        })
        .unwrap_or_default();
    let state = state.lock().await;
    if let Some(j) = state.team.jinxes.get(jinx_name) {
        let result = j.execute(&args);
        Json(
            serde_json::json!({"output": result.output, "success": result.success, "error": result.error}),
        )
    } else {
        Json(serde_json::json!({"error": format!("Jinx '{}' not found", jinx_name)}))
    }
}

async fn get_attachments(AxumPath(message_id): AxumPath<String>) -> impl IntoResponse {
    let db_path = crate::npc_sysenv::get_history_db_path();
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(history) => match history.get_message_attachments(&message_id) {
            Ok(attachments) => Json(serde_json::json!({"attachments": attachments})),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn get_global_settings() -> impl IntoResponse {
    let rc_path = crate::npc_sysenv::get_npcshrc_path();
    match std::fs::read_to_string(&rc_path) {
        Ok(content) => Json(serde_json::json!({"settings": content})),
        Err(_) => Json(serde_json::json!({"settings": ""})),
    }
}

async fn save_global_settings(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let content = body["settings"].as_str().unwrap_or("");
    let rc_path = crate::npc_sysenv::get_npcshrc_path();
    if let Some(parent) = rc_path.parent() {
        let _ = std::fs::create_dir_all(parent);
    }
    match std::fs::write(&rc_path, content) {
        Ok(_) => Json(serde_json::json!({"status": "saved"})),
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn get_project_settings(State(state): State<AppState>) -> impl IntoResponse {
    let state = state.lock().await;
    Json(serde_json::json!({
        "model": state.team.model,
        "provider": state.team.provider,
        "context": state.team.context,
        "forenpc": state.team.forenpc,
        "npcs": state.team.npc_names(),
        "jinxes": state.team.jinx_names(),
    }))
}

async fn save_project_settings(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let mut state = state.lock().await;
    if let Some(model) = body["model"].as_str() {
        state.team.model = Some(model.to_string());
    }
    if let Some(provider) = body["provider"].as_str() {
        state.team.provider = Some(provider.to_string());
    }
    if let Some(context) = body["context"].as_str() {
        state.team.context = Some(context.to_string());
    }
    Json(serde_json::json!({"status": "saved"}))
}

async fn extract_memories(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let messages = body["messages"].as_array();
    let npc_name = body["npc"].as_str().unwrap_or("assistant");
    let model = body["model"].as_str();
    let provider = body["provider"].as_str();

    if let Some(msgs) = messages {
        let conversation: String = msgs
            .iter()
            .filter_map(|m| {
                let role = m["role"].as_str().unwrap_or("");
                let content = m["content"].as_str().unwrap_or("");
                if content.is_empty() {
                    None
                } else {
                    Some(format!("{}: {}", role, content))
                }
            })
            .collect::<Vec<_>>()
            .join("\n");

        let m = model.unwrap_or("qwen3.5:2b");
        let p = provider.unwrap_or("ollama");
        let prompt = format!(
            "Extract memories from this conversation that would be useful to remember about the user.\n\n{}\n\nReturn JSON: {{\"memories\": [\"memory1\", \"memory2\"]}}",
            conversation
        );
        match crate::llm_funcs::get_llm_response_ext(
            &prompt,
            None,
            Some(m),
            Some(p),
            None,
            &[],
            None,
            Some("json"),
            None,
            false,
            None,
        )
        .await
        {
            Ok(result) => {
                let memories = result
                    .response_json
                    .and_then(|j| j.get("memories").and_then(|m| m.as_array()).cloned())
                    .unwrap_or_default();
                Json(serde_json::json!({"memories": memories, "npc": npc_name}))
            }
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        }
    } else {
        Json(serde_json::json!({"error": "Missing messages"}))
    }
}

async fn api_command(
    State(state): State<AppState>,
    AxumPath(command): AxumPath<String>,
) -> impl IntoResponse {
    let (npc, model, provider) = {
        let s = state.lock().await;
        let npc = s.team.get_npc(&s.active_npc_name).cloned();
        let m = npc
            .as_ref()
            .map(|n| n.resolved_model())
            .unwrap_or_else(|| "qwen3.5:2b".into());
        let p = npc
            .as_ref()
            .map(|n| n.resolved_provider())
            .unwrap_or_else(|| "ollama".into());
        (npc, m, p)
    };
    match crate::llm_funcs::get_llm_response(
        &command,
        npc.as_ref(),
        Some(&model),
        Some(&provider),
        None,
        &[],
        None,
    )
    .await
    {
        Ok(result) => Json(serde_json::json!({"response": result.response, "model": result.model})),
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn get_pending_memories_route() -> impl IntoResponse {
    let db_path = crate::npc_sysenv::get_history_db_path();
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(h) => match h.get_pending_memories() {
            Ok(mems) => Json(
                serde_json::json!({"memories": mems.iter().map(|(id, npc, content)| serde_json::json!({"id": id, "npc": npc, "content": content})).collect::<Vec<_>>()}),
            ),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn approve_memories(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let db_path = crate::npc_sysenv::get_history_db_path();
    let memory_ids = body["ids"].as_array();
    let status = body["status"].as_str().unwrap_or("approved");
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(h) => {
            if let Some(ids) = memory_ids {
                for id in ids {
                    if let Some(id) = id.as_i64() {
                        let _ = h.update_memory_status(id, status, None);
                    }
                }
            }
            Json(serde_json::json!({"status": "updated"}))
        }
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn search_memories(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let query = body["query"].as_str().unwrap_or("");
    let npc = body["npc"].as_str();
    let limit = body["limit"].as_u64().unwrap_or(20) as usize;
    let db_path = crate::npc_sysenv::get_history_db_path();
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(h) => match h.search_memory(query, npc, None, limit) {
            Ok(results) => Json(serde_json::json!({"results": results})),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn search_conversations_route(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let query = body["query"].as_str().unwrap_or("");
    let db_path = crate::npc_sysenv::get_history_db_path();
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(h) => match h.search_conversations(query) {
            Ok(results) => {
                let items: Vec<serde_json::Value> = results.iter().map(|m| serde_json::json!({"message_id": m.message_id, "role": m.role, "content": m.content})).collect();
                Json(serde_json::json!({"results": items}))
            }
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn get_conversation_messages(AxumPath(id): AxumPath<String>) -> impl IntoResponse {
    let db_path = crate::npc_sysenv::get_history_db_path();
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(h) => match h.load_conversation_messages(&id) {
            Ok(msgs) => {
                let items: Vec<serde_json::Value> = msgs.iter().map(|m| serde_json::json!({"message_id": m.message_id, "role": m.role, "content": m.content, "model": m.model, "npc": m.npc})).collect();
                Json(serde_json::json!({"messages": items}))
            }
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn delete_message(
    AxumPath((conv_id, msg_id)): AxumPath<(String, String)>,
) -> impl IntoResponse {
    let db_path = crate::npc_sysenv::get_history_db_path();
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(h) => match h.delete_message(&conv_id, &msg_id) {
            Ok(_) => Json(serde_json::json!({"status": "deleted"})),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn save_npc(
    State(state): State<AppState>,
    Json(body): Json<serde_json::Value>,
) -> impl IntoResponse {
    let name = body["name"].as_str().unwrap_or("");
    let state = state.lock().await;
    if let Some(npc) = state.team.get_npc(name) {
        match npc.save(state.team.source_dir.as_deref()) {
            Ok(_) => Json(serde_json::json!({"status": "saved"})),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        }
    } else {
        Json(serde_json::json!({"error": format!("NPC '{}' not found", name)}))
    }
}

async fn team_sync_status() -> impl IntoResponse {
    match crate::npc_sysenv::team_sync_status(None) {
        Ok(status) => Json(serde_json::json!(status)),
        Err(e) => Json(serde_json::json!({"error": e})),
    }
}

async fn team_sync_init() -> impl IntoResponse {
    match crate::npc_sysenv::team_sync_init(None) {
        Ok(msg) => Json(serde_json::json!({"status": msg})),
        Err(e) => Json(serde_json::json!({"error": e})),
    }
}

async fn team_sync_pull() -> impl IntoResponse {
    match crate::npc_sysenv::team_sync_pull(None) {
        Ok(msg) => Json(serde_json::json!({"status": msg})),
        Err(e) => Json(serde_json::json!({"error": e})),
    }
}

async fn team_sync_commit(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let message = body["message"].as_str().unwrap_or("Update NPC team");
    match crate::npc_sysenv::team_sync_commit(None, message) {
        Ok(msg) => Json(serde_json::json!({"status": msg})),
        Err(e) => Json(serde_json::json!({"error": e})),
    }
}

async fn team_sync_diff() -> impl IntoResponse {
    match crate::npc_sysenv::team_sync_diff(None, None) {
        Ok(diff) => Json(serde_json::json!({"diff": diff})),
        Err(e) => Json(serde_json::json!({"error": e})),
    }
}

async fn ollama_status() -> impl IntoResponse {
    match reqwest::get("http://localhost:11434/api/tags").await {
        Ok(resp) if resp.status().is_success() => Json(serde_json::json!({"status": "running"})),
        _ => Json(serde_json::json!({"status": "not_running"})),
    }
}

async fn get_ollama_models() -> impl IntoResponse {
    match reqwest::get("http://localhost:11434/api/tags").await {
        Ok(resp) => match resp.json::<serde_json::Value>().await {
            Ok(data) => Json(data),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn generate_video_api(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let prompt = body["prompt"].as_str().unwrap_or("");
    let model = body["model"].as_str();
    let provider = body["provider"].as_str();
    let output = body["output_path"].as_str().unwrap_or("/tmp/npc_video.mp4");
    match crate::llm_funcs::gen_video(prompt, model, provider, None, output).await {
        Ok(result) => Json(serde_json::json!(result)),
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn get_npc_executions_route(State(state): State<AppState>) -> impl IntoResponse {
    let npc_name = { state.lock().await.active_npc_name.clone() };
    let db_path = crate::npc_sysenv::get_history_db_path();
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(h) => match h.get_npc_executions(&npc_name, 100) {
            Ok(execs) => Json(serde_json::json!({"executions": execs})),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

async fn get_jinx_executions_route(Json(body): Json<serde_json::Value>) -> impl IntoResponse {
    let jinx_name = body["jinx_name"].as_str();
    let db_path = crate::npc_sysenv::get_history_db_path();
    match crate::memory::CommandHistory::open(&db_path) {
        Ok(h) => match h.get_jinx_executions(jinx_name, 100) {
            Ok(execs) => Json(serde_json::json!({"executions": execs})),
            Err(e) => Json(serde_json::json!({"error": e.to_string()})),
        },
        Err(e) => Json(serde_json::json!({"error": e.to_string()})),
    }
}

pub async fn start_http_server(state: AppState, config: &ServerConfig) -> Result<()> {
    let app = create_app(state);
    let addr = format!("{}:{}", config.host, config.http_port);
    let listener = tokio::net::TcpListener::bind(&addr)
        .await
        .map_err(|e| NpcError::Other(format!("Bind {}: {}", addr, e)))?;
    tracing::info!("NPC server on {}", addr);
    axum::serve(listener, app)
        .await
        .map_err(|e| NpcError::Other(format!("Server: {}", e)))?;
    Ok(())
}

pub async fn start_mcp_server(state: AppState) -> Result<()> {
    use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};

    let stdin = tokio::io::stdin();
    let mut stdout = tokio::io::stdout();
    let mut reader = BufReader::new(stdin);

    let mut line = String::new();
    loop {
        line.clear();
        let n = reader
            .read_line(&mut line)
            .await
            .map_err(|e| NpcError::Other(e.to_string()))?;
        if n == 0 {
            break;
        }

        let trimmed = line.trim();
        if trimmed.is_empty() {
            continue;
        }

        let request: serde_json::Value = match serde_json::from_str(trimmed) {
            Ok(v) => v,
            Err(_) => continue,
        };

        let id = request.get("id").cloned();
        let method = request["method"].as_str().unwrap_or("");
        let params = request.get("params").cloned().unwrap_or_default();

        let result = match method {
            "initialize" => serde_json::json!({
                "protocolVersion": "2024-11-05",
                "capabilities": {"tools": {"listChanged": true}, "prompts": {"listChanged": true}},
                "serverInfo": {"name": "npcrs-mcp", "version": env!("CARGO_PKG_VERSION")}
            }),
            "tools/list" => {
                let state = state.lock().await;
                let active_npc = state.team.get_npc(&state.active_npc_name);
                let jinx_names: Vec<&str> = if let Some(npc) = active_npc {
                    npc.jinx_names.iter().map(|s| s.as_str()).collect()
                } else {
                    state.team.jinx_names()
                };
                let tools: Vec<serde_json::Value> = jinx_names
                    .iter()
                    .filter_map(|name| state.team.jinxes.get(*name))
                    .filter_map(|jinx| jinx.to_tool_def())
                    .map(|td| serde_json::to_value(&td).unwrap_or_default())
                    .collect();
                serde_json::json!({"tools": tools})
            }
            "tools/call" => {
                let tool_name = params["name"].as_str().unwrap_or("");
                let args: HashMap<String, String> = params["arguments"]
                    .as_object()
                    .map(|obj| {
                        obj.iter()
                            .map(|(k, v)| (k.clone(), v.as_str().unwrap_or("").to_string()))
                            .collect()
                    })
                    .unwrap_or_default();
                let state = state.lock().await;
                if let Some(j) = state.team.jinxes.get(tool_name) {
                    let result = j.execute(&args);
                    serde_json::json!({"content": [{"type": "text", "text": result.output}], "isError": !result.success})
                } else {
                    serde_json::json!({"content": [{"type": "text", "text": format!("Tool '{}' not found", tool_name)}], "isError": true})
                }
            }
            "prompts/list" => {
                let state = state.lock().await;
                let prompts: Vec<serde_json::Value> = state.team.npc_names().iter()
                    .map(|name| serde_json::json!({"name": format!("npc_{}", name), "description": format!("Switch to {}", name)}))
                    .collect();
                serde_json::json!({"prompts": prompts})
            }
            "notifications/initialized" => continue,
            _ => {
                serde_json::json!({"error": {"code": -32601, "message": format!("Method not found: {}", method)}})
            }
        };

        if let Some(id) = id {
            let response = serde_json::json!({"jsonrpc": "2.0", "id": id, "result": result});
            let s = serde_json::to_string(&response).unwrap_or_default();
            let header = format!("Content-Length: {}\r\n\r\n", s.len());
            stdout
                .write_all(header.as_bytes())
                .await
                .map_err(|e| NpcError::Other(e.to_string()))?;
            stdout
                .write_all(s.as_bytes())
                .await
                .map_err(|e| NpcError::Other(e.to_string()))?;
            stdout
                .flush()
                .await
                .map_err(|e| NpcError::Other(e.to_string()))?;
        }
    }
    Ok(())
}

pub async fn start_servers(team: Team, config: ServerConfig) -> Result<()> {
    let active_npc = team
        .lead_npc()
        .map(|n| n.name.clone())
        .unwrap_or_else(|| "assistant".to_string());
    let state = Arc::new(Mutex::new(ServerState {
        team,
        active_npc_name: active_npc,
        conversations: HashMap::new(),
    }));

    if config.mcp_enabled {
        let http_state = Arc::clone(&state);
        let mcp_state = Arc::clone(&state);
        tokio::select! {
            result = start_http_server(http_state, &config) => { if let Err(e) = result { tracing::error!("HTTP: {}", e); } }
            result = start_mcp_server(mcp_state) => { if let Err(e) = result { tracing::error!("MCP: {}", e); } }
        }
    } else {
        start_http_server(state, &config).await?;
    }
    Ok(())
}
