use std::process::Command;

use crate::core::agent::{Agent, ClaudeCodeAdapter, CodexAdapter};
use crate::core::store::Store;
use crate::core::sync::sync_dirs;
use crate::core::{ClosureError, Result};

use super::args::{AuthCommands, Cli, Commands, ProviderAddEditArgs, ProviderCommands};

pub fn run(cli: Cli) -> Result<()> {
    let closure_dir = dirs::home_dir()
        .ok_or_else(|| ClosureError::Io(std::io::Error::new(
            std::io::ErrorKind::NotFound,
            "home directory not found"
        )))?
        .join(".kozbox")
        .join("closure");

    let workdata_dir = closure_dir.join("workdata");
    let sync_dir = closure_dir.join("sync");
    std::fs::create_dir_all(&workdata_dir)?;

    let store = Store::new(&workdata_dir);

    match cli.command {
        Some(Commands::Auth { command }) => match command {
            Some(AuthCommands::Logout) => {
                store.logout()?;
                println!("已锁定。需要的时候再来——我一般在采购部和工程部之间来回跑。");
                Ok(())
            }
            None => cmd_auth(&store),
        },
        Some(Commands::Init) => {
            if store.has_encrypted() {
                println!("vault 已经存在，无需重复初始化。");
                return Ok(());
            }
            let password = rpassword::prompt_password("设置 vault 密码: ")?;
            let confirm = rpassword::prompt_password("确认密码: ")?;
            if password != confirm {
                eprintln!("两次输入的密码不一致。");
                return Err(ClosureError::Crypto("password mismatch".to_string()));
            }
            store.init(&password)?;
            println!("vault 初始化完成。");
            Ok(())
        }
        Some(Commands::Sync) => cmd_sync(&workdata_dir, &sync_dir),
        Some(Commands::SetUpstream { url }) => cmd_set_upstream(&sync_dir, &url),
        Some(Commands::ClaudeCode { command }) => {
            handle_agent_command("claude-code", &store, command)
        }
        Some(Commands::Codex { command }) => {
            handle_agent_command("codex", &store, command)
        }
        None => {
            Err(ClosureError::Crypto("TUI not yet implemented".to_string()))
        }
    }
}

fn cmd_auth(store: &Store) -> Result<()> {
    if !store.has_encrypted() {
        eprintln!("vault 尚未初始化。请先运行 `closure init` 创建。");
        return Err(ClosureError::Crypto("vault not initialized".to_string()));
    }
    if store.is_authenticated() {
        println!("已经解锁了，博士。");
        return Ok(());
    }
    let password = rpassword::prompt_password("Password: ")?;
    store.authenticate(&password)?;
    println!("已解锁。欢迎回来，博士。");
    Ok(())
}

fn cmd_sync(workdata_dir: &std::path::Path, sync_dir: &std::path::Path) -> Result<()> {
    let git_dir = sync_dir.join(".git");
    if !git_dir.exists() {
        return Err(ClosureError::Crypto(
            "sync 目录不是 git 仓库。请先用 `closure set-upstream <url>` 设置同步上游。"
                .to_string(),
        ));
    }

    std::fs::create_dir_all(sync_dir)?;

    // git pull
    let pull = Command::new("git")
        .args(["-C", sync_dir.to_str().unwrap_or("."), "pull", "--ff-only"])
        .output()
        .map_err(|e| ClosureError::Crypto(format!("git pull failed: {}", e)))?;
    if !pull.status.success() {
        let stderr = String::from_utf8_lossy(&pull.stderr);
        return Err(ClosureError::Crypto(format!("git pull failed: {}", stderr)));
    }

    // Rust bidirectional sync
    sync_dirs(workdata_dir, sync_dir)?;

    // git add, commit, push
    let add = Command::new("git")
        .args(["-C", sync_dir.to_str().unwrap_or("."), "add", "-A"])
        .output()
        .map_err(|e| ClosureError::Crypto(format!("git add failed: {}", e)))?;
    if !add.status.success() {
        return Err(ClosureError::Crypto("git add failed".to_string()));
    }

    let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
    let commit = Command::new("git")
        .args([
            "-C",
            sync_dir.to_str().unwrap_or("."),
            "commit",
            "-m",
            &format!("sync: {}", timestamp),
            "--allow-empty",
        ])
        .output()
        .map_err(|e| ClosureError::Crypto(format!("git commit failed: {}", e)))?;
    if !commit.status.success() {
        let stderr = String::from_utf8_lossy(&commit.stderr);
        // "nothing to commit" is fine — repo already up to date
        if !stderr.contains("nothing to commit") {
            return Err(ClosureError::Crypto(format!("git commit failed: {}", stderr)));
        }
    }

    let push = Command::new("git")
        .args(["-C", sync_dir.to_str().unwrap_or("."), "push"])
        .output()
        .map_err(|e| ClosureError::Crypto(format!("git push failed: {}", e)))?;
    if !push.status.success() {
        let stderr = String::from_utf8_lossy(&push.stderr);
        return Err(ClosureError::Crypto(format!("git push failed: {}", stderr)));
    }

    println!("同步完成。就跟当年在卡兹戴尔阁楼里搭的那些中继器一样。");
    Ok(())
}

fn cmd_set_upstream(sync_dir: &std::path::Path, url: &str) -> Result<()> {
    let git_dir = sync_dir.join(".git");
    if git_dir.exists() {
        let set = Command::new("git")
            .args([
                "-C",
                sync_dir.to_str().unwrap_or("."),
                "remote",
                "set-url",
                "origin",
                url,
            ])
            .output()
            .map_err(|e| ClosureError::Crypto(format!("git remote set-url failed: {}", e)))?;
        if !set.status.success() {
            let stderr = String::from_utf8_lossy(&set.stderr);
            return Err(ClosureError::Crypto(format!("git remote set-url failed: {}", stderr)));
        }
        println!("sync 上游已更新为 {}", url);
    } else {
        // Clone if dir doesn't have a git repo yet
        let is_empty = sync_dir
            .read_dir()
            .map(|mut d| d.next().is_none())
            .unwrap_or(true);
        if !is_empty {
            return Err(ClosureError::Crypto(
                "sync 目录非空且不是 git 仓库，无法设置上游。请清空该目录后重试。"
                    .to_string(),
            ));
        }
        let parent = sync_dir.parent().unwrap_or_else(|| std::path::Path::new("."));
        std::fs::create_dir_all(parent)?;
        // Remove the empty sync dir so git clone can create it
        let _ = std::fs::remove_dir(sync_dir);
        let clone = Command::new("git")
            .args([
                "clone",
                url,
                sync_dir.to_str().unwrap_or("."),
            ])
            .output()
            .map_err(|e| ClosureError::Crypto(format!("git clone failed: {}", e)))?;
        if !clone.status.success() {
            let stderr = String::from_utf8_lossy(&clone.stderr);
            return Err(ClosureError::Crypto(format!("git clone failed: {}", stderr)));
        }
        println!("已克隆 sync 仓库 {} 到本地。", url);
    }
    Ok(())
}

fn handle_agent_command(
    agent_name: &str,
    store: &Store,
    command: super::args::AgentCommands,
) -> Result<()> {
    use super::args::AgentCommands;

    let AgentCommands::Provider { command } = command;

    match command {
        ProviderCommands::Select { name } => {
            let store_data = store.load()?;
            let provider = store_data
                .providers
                .iter()
                .find(|p| p.name() == name && p.target() == agent_name)
                .ok_or_else(|| ClosureError::ProviderNotFound(name.clone()))?;

            apply_provider(agent_name, provider)?;

            let mut state = store.load_state()?;
            state.insert(agent_name.to_string(), Some(name.clone()));
            store.save_state(&state)?;

            println!("已切换到 {} | {} 端点信号良好。", name, agent_name);
            Ok(())
        }
        ProviderCommands::List => {
            let store_data = store.load()?;
            let state = store.load_state()?;
            let active = state.get(agent_name).cloned().flatten();

            let providers: Vec<_> = store_data.providers.iter()
                .filter(|p| p.target() == agent_name)
                .collect();
            if providers.is_empty() {
                println!("{} 还没有配置接入点。", agent_name);
                println!("没有就自己搭一个呗？");
                return Ok(());
            }
            println!("{} 可用接入点:", agent_name);
            for provider in &providers {
                let marker = active
                    .as_ref()
                    .filter(|a| *a == provider.name())
                    .map(|_| " (*)")
                    .unwrap_or("");
                println!("  {}{}", provider.name(), marker);
            }
            Ok(())
        }
        ProviderCommands::Add { name, args } => {
            cmd_provider_add(agent_name, store, &name, &args)
        }
        ProviderCommands::Edit { name, args } => {
            cmd_provider_edit(agent_name, store, &name, &args)
        }
        ProviderCommands::Remove { name } => {
            let mut store_data = store.load()?;
            let idx = store_data
                .providers
                .iter()
                .position(|p| p.name() == name && p.target() == agent_name)
                .ok_or_else(|| ClosureError::ProviderNotFound(name.clone()))?;
            store_data.providers.remove(idx);
            store.save(&store_data)?;
            println!("Removed provider '{}'.", name);
            Ok(())
        }
    }
}

fn cmd_provider_add(
    agent_name: &str,
    store: &Store,
    name: &str,
    args: &ProviderAddEditArgs,
) -> Result<()> {
    validate_agent_args(agent_name, args)?;

    let mut store_data = store.load()?;
    let exists = store_data
        .providers
        .iter()
        .any(|p| p.name() == name && p.target() == agent_name);
    if exists {
        return Err(ClosureError::Crypto(format!(
            "provider '{}' already exists for {}",
            name, agent_name
        )));
    }

    let provider = build_provider(name, agent_name, args)?;
    store_data.providers.push(provider);
    store.save(&store_data)?;

    println!("已添加 provider '{}' ({})。", name, agent_name);
    Ok(())
}

fn cmd_provider_edit(
    agent_name: &str,
    store: &Store,
    name: &str,
    args: &ProviderAddEditArgs,
) -> Result<()> {
    validate_agent_args(agent_name, args)?;

    let mut store_data = store.load()?;
    let idx = store_data
        .providers
        .iter()
        .position(|p| p.name() == name && p.target() == agent_name)
        .ok_or_else(|| ClosureError::ProviderNotFound(name.to_string()))?;

    let model = args.model.as_deref();
    let key = args.key.as_deref();
    let url = args.url.as_deref();
    let model_provider = args.model_provider.as_deref();
    let review_model = args.review_model.as_deref();
    store_data.providers[idx].update(
        model, key, url, model_provider, review_model, &args.env,
    );
    store.save(&store_data)?;

    // If this was the active provider, re-apply to refresh agent config
    let state = store.load_state()?;
    if state
        .get(agent_name)
        .cloned()
        .flatten()
        .as_deref()
        == Some(name)
    {
        apply_provider(agent_name, &store_data.providers[idx])?;
    }

    println!("已更新 provider '{}' ({})。", name, agent_name);
    Ok(())
}

fn validate_agent_args(agent_name: &str, args: &ProviderAddEditArgs) -> Result<()> {
    let has_codex_flags = args.key.is_some()
        || args.url.is_some()
        || args.model_provider.is_some()
        || args.review_model.is_some();
    if agent_name == "claude-code" && has_codex_flags {
        return Err(ClosureError::Crypto(
            "--key / --url / --provider / --review-model 仅适用于 codex。"
                .to_string(),
        ));
    }
    if agent_name == "codex" && !args.env.is_empty() {
        return Err(ClosureError::Crypto(
            "--env 仅适用于 claude-code。".to_string(),
        ));
    }
    Ok(())
}

fn build_provider(name: &str, agent_name: &str, args: &ProviderAddEditArgs) -> Result<crate::core::provider::Provider> {
    use crate::core::provider::Provider;
    match agent_name {
        "claude-code" => {
            let env: std::collections::HashMap<String, String> =
                args.env.iter().cloned().collect();
            Ok(Provider::ClaudeCode {
                name: name.to_string(),
                model: args.model.clone(),
                env,
            })
        }
        "codex" => Ok(Provider::Codex {
            name: name.to_string(),
            api_key: args.key.clone(),
            base_url: args.url.clone(),
            model: args.model.clone(),
            model_provider: args.model_provider.clone(),
            review_model: args.review_model.clone(),
        }),
        _ => Err(ClosureError::AgentConfigNotFound(agent_name.to_string())),
    }
}

fn apply_provider(agent_name: &str, provider: &crate::core::provider::Provider) -> Result<()> {
    match agent_name {
        "claude-code" => {
            let config_path = dirs::home_dir()
                .ok_or_else(|| ClosureError::Io(std::io::Error::new(
                    std::io::ErrorKind::NotFound,
                    "home directory not found"
                )))?
                .join(".claude")
                .join("settings.json");
            let adapter = ClaudeCodeAdapter::new(&config_path);
            adapter.apply(provider)
        }
        "codex" => {
            let home = dirs::home_dir().ok_or_else(|| ClosureError::Io(std::io::Error::new(
                std::io::ErrorKind::NotFound,
                "home directory not found"
            )))?;
            let config_path = home.join(".codex").join("config.toml");
            let auth_path = home.join(".codex").join("auth.json");
            let adapter = CodexAdapter::new(&config_path, &auth_path);
            adapter.apply(provider)
        }
        _ => Err(ClosureError::AgentConfigNotFound(agent_name.to_string())),
    }
}
