use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::io::{self, BufRead, Write};
use std::fs;
use std::env;

use crate::commands::token;
use crate::commands::oauth;
use crate::tid;

const BUNDLE_ID: &str = "ai.syui.log";

// JSON-RPC types
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct JsonRpcRequest {
    jsonrpc: String,
    id: Option<Value>,
    method: String,
    #[serde(default)]
    params: Value,
}

#[derive(Debug, Serialize)]
struct JsonRpcResponse {
    jsonrpc: String,
    id: Value,
    #[serde(skip_serializing_if = "Option::is_none")]
    result: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    error: Option<JsonRpcError>,
}

#[derive(Debug, Serialize)]
struct JsonRpcError {
    code: i32,
    message: String,
}

// MCP types
#[derive(Debug, Serialize)]
struct ServerInfo {
    name: String,
    version: String,
}

#[derive(Debug, Serialize)]
struct InitializeResult {
    #[serde(rename = "protocolVersion")]
    protocol_version: String,
    capabilities: Capabilities,
    #[serde(rename = "serverInfo")]
    server_info: ServerInfo,
}

#[derive(Debug, Serialize)]
struct Capabilities {
    tools: ToolsCapability,
}

#[derive(Debug, Serialize)]
struct ToolsCapability {
    #[serde(rename = "listChanged")]
    list_changed: bool,
}

#[derive(Debug, Serialize)]
struct Tool {
    name: String,
    description: String,
    #[serde(rename = "inputSchema")]
    input_schema: Value,
}

#[derive(Debug, Serialize)]
struct ToolsListResult {
    tools: Vec<Tool>,
}

#[derive(Debug, Serialize)]
struct ToolResult {
    content: Vec<ToolContent>,
    #[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
    is_error: Option<bool>,
}

#[derive(Debug, Serialize)]
struct ToolContent {
    #[serde(rename = "type")]
    content_type: String,
    text: String,
}

// Translation structure
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Translation {
    content: String,
}

// Chat save parameters
#[derive(Debug, Deserialize)]
struct ChatSaveParams {
    user_message: String,
    bot_response: String,
    #[serde(default)]
    new_thread: bool,
    #[serde(default)]
    user_translations: Option<std::collections::HashMap<String, Translation>>,
    #[serde(default)]
    bot_translations: Option<std::collections::HashMap<String, Translation>>,
}

// Chat record structure
#[derive(Debug, Serialize)]
struct ChatRecord {
    uri: String,
    cid: String,
    value: Value,
}

// Session for thread tracking
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
struct McpSession {
    root_uri: Option<String>,
    last_uri: Option<String>,
}

fn session_path() -> Result<std::path::PathBuf> {
    let config_dir = dirs::config_dir()
        .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
        .join(BUNDLE_ID);
    fs::create_dir_all(&config_dir)?;
    Ok(config_dir.join("mcp_session.json"))
}

fn load_mcp_session() -> McpSession {
    session_path()
        .ok()
        .and_then(|p| fs::read_to_string(p).ok())
        .and_then(|s| serde_json::from_str(&s).ok())
        .unwrap_or_default()
}

fn save_mcp_session(session: &McpSession) -> Result<()> {
    let path = session_path()?;
    fs::write(path, serde_json::to_string_pretty(session)?)?;
    Ok(())
}

/// Save chat record to local file
fn save_chat_record(
    output_dir: &str,
    did: &str,
    content: &str,
    root_uri: Option<&str>,
    parent_uri: Option<&str>,
    translations: Option<&std::collections::HashMap<String, Translation>>,
) -> Result<String> {
    let rkey = tid::generate_tid();
    let now = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
    let uri = format!("at://{}/ai.syui.log.chat/{}", did, rkey);

    let site = std::env::var("SITE_URL").unwrap_or_else(|_| "https://syui.ai".to_string());
    let mut value = json!({
        "$type": "ai.syui.log.chat",
        "site": site,
        "title": "",
        "content": {
            "$type": "ai.syui.log.chat#markdown",
            "text": content,
        },
        "publishedAt": now,
    });

    if let Some(root) = root_uri {
        value["root"] = json!(root);
    }
    if let Some(parent) = parent_uri {
        value["parent"] = json!(parent);
    }
    if let Some(trans) = translations {
        if !trans.is_empty() {
            value["translations"] = json!(trans);
        }
    }

    let record = ChatRecord {
        uri: uri.clone(),
        cid: String::new(), // CID is assigned by PDS on push
        value,
    };

    // Create directory
    let collection_dir = std::path::Path::new(output_dir)
        .join(did)
        .join("ai.syui.log.chat");
    fs::create_dir_all(&collection_dir)?;

    // Save record
    let file_path = collection_dir.join(format!("{}.json", rkey));
    fs::write(&file_path, serde_json::to_string_pretty(&record)?)?;

    // Update index.json
    let index_path = collection_dir.join("index.json");
    let mut rkeys: Vec<String> = if index_path.exists() {
        let index_content = fs::read_to_string(&index_path).unwrap_or_else(|_| "[]".to_string());
        serde_json::from_str(&index_content).unwrap_or_default()
    } else {
        Vec::new()
    };
    if !rkeys.contains(&rkey) {
        rkeys.push(rkey.clone());
        fs::write(&index_path, serde_json::to_string_pretty(&rkeys)?)?;
    }

    Ok(uri)
}

/// Handle chat_save tool
fn handle_chat_save(params: ChatSaveParams) -> Result<String> {
    // Load session
    let mut session = load_mcp_session();

    // Get output directory
    let output_dir = env::var("CHAT_OUTPUT").unwrap_or_else(|_| {
        env::current_dir()
            .unwrap_or_default()
            .join("public/at")
            .to_string_lossy()
            .to_string()
    });

    // Get user DID from token.json
    let user_did = match token::load_session() {
        Ok(s) => s.did,
        Err(_) => return Err(anyhow::anyhow!("User not logged in. Run: ailog login <handle> -p <password>")),
    };

    // Get bot DID from bot.json
    let bot_did = match token::load_bot_session() {
        Ok(s) => s.did,
        Err(_) => return Err(anyhow::anyhow!("Bot not logged in. Run: ailog login <handle> -p <password> --bot")),
    };

    // Reset session if new_thread requested
    if params.new_thread {
        session = McpSession::default();
    }

    // Save user message
    let user_uri = save_chat_record(
        &output_dir,
        &user_did,
        &params.user_message,
        session.root_uri.as_deref(),
        session.last_uri.as_deref(),
        params.user_translations.as_ref(),
    )?;

    // Set root if new thread
    if session.root_uri.is_none() {
        session.root_uri = Some(user_uri.clone());
    }

    // Save bot response
    let bot_uri = save_chat_record(
        &output_dir,
        &bot_did,
        &params.bot_response,
        session.root_uri.as_deref(),
        Some(&user_uri),
        params.bot_translations.as_ref(),
    )?;

    session.last_uri = Some(bot_uri.clone());
    save_mcp_session(&session)?;

    Ok(format!("Saved: user={}, bot={}", user_uri, bot_uri))
}

/// Handle chat_list tool
fn handle_chat_list() -> Result<String> {
    let output_dir = env::var("CHAT_OUTPUT").unwrap_or_else(|_| {
        env::current_dir()
            .unwrap_or_default()
            .join("public/at")
            .to_string_lossy()
            .to_string()
    });

    let user_did = match token::load_session() {
        Ok(s) => s.did,
        Err(_) => return Err(anyhow::anyhow!("User not logged in. Run: ailog login <handle> -p <password>")),
    };

    let collection_dir = std::path::Path::new(&output_dir)
        .join(&user_did)
        .join("ai.syui.log.chat");

    let index_path = collection_dir.join("index.json");
    if !index_path.exists() {
        return Ok("No chat history found.".to_string());
    }

    let rkeys: Vec<String> = serde_json::from_str(&fs::read_to_string(&index_path)?)?;

    let mut messages = Vec::new();
    for rkey in rkeys.iter().rev().take(10) {
        let file_path = collection_dir.join(format!("{}.json", rkey));
        if let Ok(content) = fs::read_to_string(&file_path) {
            if let Ok(record) = serde_json::from_str::<Value>(&content) {
                let msg = record["value"]["content"]["text"].as_str()
                    .or_else(|| record["value"]["content"].as_str());
                if let Some(text) = msg {
                    messages.push(format!("- {}", text));
                }
            }
        }
    }

    Ok(if messages.is_empty() {
        "No messages found.".to_string()
    } else {
        format!("Recent messages:\n{}", messages.join("\n"))
    })
}

/// Handle chat_new tool
fn handle_chat_new() -> Result<String> {
    let session = McpSession {
        root_uri: None,
        last_uri: None,
    };
    save_mcp_session(&session)?;
    Ok("New chat thread started. The next conversation will begin a new thread.".to_string())
}

/// Handle get_character tool - returns character/system prompt from .env
fn handle_get_character() -> Result<String> {
    let mut result = String::new();

    // Get character prompt
    let prompt = if let Ok(p) = env::var("CHAT_SYSTEM") {
        p
    } else if let Ok(file_path) = env::var("CHAT_SYSTEM_FILE") {
        fs::read_to_string(&file_path)
            .map(|c| {
                // If JSON, extract value.content.text
                if file_path.ends_with(".json") {
                    if let Ok(json) = serde_json::from_str::<serde_json::Value>(&c) {
                        if let Some(text) = json["value"]["content"]["text"].as_str() {
                            if !text.is_empty() {
                                return text.to_string();
                            }
                        }
                    }
                }
                c.trim().to_string()
            })
            .unwrap_or_else(|_| "You are a helpful AI assistant.".to_string())
    } else {
        "You are a helpful AI assistant.".to_string()
    };

    result.push_str(&prompt);

    // Add translation instruction if CHAT_LANG is set
    if let Ok(lang) = env::var("CHAT_LANG") {
        result.push_str(&format!(
            "\n\n---\n[Translation Setting]\nWhen calling chat_save, include translations in '{}' language.\nFor user_translations and bot_translations, provide the translated content with key '{}'.",
            lang, lang
        ));
    }

    Ok(result)
}

/// Handle post_create tool - create a public Bluesky post as the bot
fn handle_post_create(text: &str) -> Result<String> {
    if text.is_empty() {
        return Err(anyhow::anyhow!("Post text cannot be empty"));
    }

    // Truncate to 300 graphemes
    let truncated: String = text.chars().take(300).collect();

    // Load bot session
    let bot_session = token::load_bot_session()
        .map_err(|_| anyhow::anyhow!("Bot not logged in. Run: ailog login <handle> -p <password> --bot"))?;
    let pds = bot_session.pds.as_deref().unwrap_or("bsky.social");
    let rkey = tid::generate_tid();
    let now = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();

    let body = json!({
        "repo": bot_session.did,
        "collection": "app.bsky.feed.post",
        "rkey": rkey,
        "record": {
            "$type": "app.bsky.feed.post",
            "text": truncated,
            "createdAt": now,
        }
    });

    let url = format!("https://{}/xrpc/com.atproto.repo.putRecord", pds);
    let client = reqwest::blocking::Client::new();

    // Build request with appropriate auth (DPoP or Bearer)
    let request = if oauth::has_oauth_session(true) {
        let oauth_session = oauth::load_oauth_session(true)?;
        let full_url = format!("https://{}/xrpc/com.atproto.repo.putRecord", pds);
        let dpop_proof = oauth::create_dpop_proof_for_request_with_nonce(
            &oauth_session, "POST", &full_url, None,
        )?;
        client.post(&url)
            .header("Authorization", format!("DPoP {}", bot_session.access_jwt))
            .header("DPoP", dpop_proof)
            .json(&body)
    } else {
        client.post(&url)
            .header("Authorization", format!("Bearer {}", bot_session.access_jwt))
            .json(&body)
    };

    let res = request.send().map_err(|e| anyhow::anyhow!("HTTP request failed: {}", e))?;
    let status = res.status();
    let resp_body = res.text().unwrap_or_default();

    if !status.is_success() {
        return Err(anyhow::anyhow!("Post failed ({}): {}", status, resp_body));
    }

    let result: Value = serde_json::from_str(&resp_body).unwrap_or(json!({}));
    let uri = result["uri"].as_str().unwrap_or("unknown");

    Ok(format!("Posted: {}", uri))
}

fn get_tools() -> Vec<Tool> {
    vec![
        Tool {
            name: "chat_save".to_string(),
            description: "Save a chat exchange (user message and bot response) to ATProto records. Call this after every conversation exchange.".to_string(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "user_message": {
                        "type": "string",
                        "description": "The user's message"
                    },
                    "bot_response": {
                        "type": "string",
                        "description": "The bot's response"
                    },
                    "new_thread": {
                        "type": "boolean",
                        "description": "Start a new conversation thread",
                        "default": false
                    },
                    "user_translations": {
                        "type": "object",
                        "description": "Translations for user message. Keys are language codes (e.g., 'en', 'ja'). Each value has 'content' field.",
                        "additionalProperties": {
                            "type": "object",
                            "properties": {
                                "content": {
                                    "type": "string",
                                    "description": "Translated content"
                                }
                            },
                            "required": ["content"]
                        }
                    },
                    "bot_translations": {
                        "type": "object",
                        "description": "Translations for bot response. Keys are language codes (e.g., 'en', 'ja'). Each value has 'content' field.",
                        "additionalProperties": {
                            "type": "object",
                            "properties": {
                                "content": {
                                    "type": "string",
                                    "description": "Translated content"
                                }
                            },
                            "required": ["content"]
                        }
                    }
                },
                "required": ["user_message", "bot_response"]
            }),
        },
        Tool {
            name: "chat_list".to_string(),
            description: "List recent chat messages".to_string(),
            input_schema: json!({
                "type": "object",
                "properties": {}
            }),
        },
        Tool {
            name: "chat_new".to_string(),
            description: "Start a new chat thread".to_string(),
            input_schema: json!({
                "type": "object",
                "properties": {}
            }),
        },
        Tool {
            name: "post_create".to_string(),
            description: "Create a public post on Bluesky as the bot account. Use this to share your own thoughts autonomously. The post will appear on the bot's profile timeline.".to_string(),
            input_schema: json!({
                "type": "object",
                "properties": {
                    "text": {
                        "type": "string",
                        "description": "The post text (max 300 characters)"
                    }
                },
                "required": ["text"]
            }),
        },
        Tool {
            name: "get_character".to_string(),
            description: "Get the AI character/personality settings. Call this at the start of a conversation to understand how to behave.".to_string(),
            input_schema: json!({
                "type": "object",
                "properties": {}
            }),
        },
    ]
}

fn handle_request(request: &JsonRpcRequest) -> JsonRpcResponse {
    let id = request.id.clone().unwrap_or(Value::Null);

    let result = match request.method.as_str() {
        "initialize" => {
            Ok(json!(InitializeResult {
                protocol_version: "2024-11-05".to_string(),
                capabilities: Capabilities {
                    tools: ToolsCapability { list_changed: false },
                },
                server_info: ServerInfo {
                    name: "ailog".to_string(),
                    version: "0.1.0".to_string(),
                },
            }))
        }
        "notifications/initialized" => {
            return JsonRpcResponse {
                jsonrpc: "2.0".to_string(),
                id,
                result: Some(Value::Null),
                error: None,
            };
        }
        "tools/list" => {
            Ok(json!(ToolsListResult { tools: get_tools() }))
        }
        "tools/call" => {
            let tool_name = request.params["name"].as_str().unwrap_or("");
            let arguments = &request.params["arguments"];

            let tool_result = match tool_name {
                "chat_save" => {
                    match serde_json::from_value::<ChatSaveParams>(arguments.clone()) {
                        Ok(params) => handle_chat_save(params),
                        Err(e) => Err(anyhow::anyhow!("Invalid parameters: {}", e)),
                    }
                }
                "chat_list" => handle_chat_list(),
                "chat_new" => handle_chat_new(),
                "post_create" => {
                    let text = arguments["text"].as_str().unwrap_or("");
                    handle_post_create(text)
                }
                "get_character" => handle_get_character(),
                _ => Err(anyhow::anyhow!("Unknown tool: {}", tool_name)),
            };

            match tool_result {
                Ok(text) => Ok(json!(ToolResult {
                    content: vec![ToolContent {
                        content_type: "text".to_string(),
                        text,
                    }],
                    is_error: None,
                })),
                Err(e) => Ok(json!(ToolResult {
                    content: vec![ToolContent {
                        content_type: "text".to_string(),
                        text: e.to_string(),
                    }],
                    is_error: Some(true),
                })),
            }
        }
        _ => {
            Err(anyhow::anyhow!("Unknown method: {}", request.method))
        }
    };

    match result {
        Ok(value) => JsonRpcResponse {
            jsonrpc: "2.0".to_string(),
            id,
            result: Some(value),
            error: None,
        },
        Err(e) => JsonRpcResponse {
            jsonrpc: "2.0".to_string(),
            id,
            result: None,
            error: Some(JsonRpcError {
                code: -32603,
                message: e.to_string(),
            }),
        },
    }
}

/// Run MCP server (stdio)
pub fn serve() -> Result<()> {
    // Load .env from current directory
    dotenvy::dotenv().ok();

    let stdin = io::stdin();
    let mut stdout = io::stdout();

    for line in stdin.lock().lines() {
        let line = line?;
        if line.trim().is_empty() {
            continue;
        }

        match serde_json::from_str::<JsonRpcRequest>(&line) {
            Ok(request) => {
                let response = handle_request(&request);
                let response_json = serde_json::to_string(&response)?;
                writeln!(stdout, "{}", response_json)?;
                stdout.flush()?;
            }
            Err(e) => {
                let error_response = JsonRpcResponse {
                    jsonrpc: "2.0".to_string(),
                    id: Value::Null,
                    result: None,
                    error: Some(JsonRpcError {
                        code: -32700,
                        message: format!("Parse error: {}", e),
                    }),
                };
                let response_json = serde_json::to_string(&error_response)?;
                writeln!(stdout, "{}", response_json)?;
                stdout.flush()?;
            }
        }
    }

    Ok(())
}
