use serde::Serialize;
use std::path::{Path, PathBuf};

pub struct ClaudeLauncher {
    pub file: String,
    pub args_prefix: Vec<String>,
}

/// 供设置页展示的解析结果
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ClaudePathInfo {
    /// 实际将执行的命令（兜底时为 "claude"）
    pub display: String,
    /// manual | manual-invalid | path | known | fallback
    pub source: String,
}

/// 按扩展名构造启动方式：.cmd/.bat 无法被 CreateProcess 直接执行，需经 cmd /c
fn launcher_for(file: &Path) -> ClaudeLauncher {
    let ext = file
        .extension()
        .and_then(|e| e.to_str())
        .map(|e| e.to_ascii_lowercase())
        .unwrap_or_default();
    let path = file.to_string_lossy().into_owned();
    if ext == "cmd" || ext == "bat" {
        ClaudeLauncher {
            file: "cmd.exe".into(),
            args_prefix: vec!["/c".into(), path],
        }
    } else {
        ClaudeLauncher {
            file: path,
            args_prefix: vec![],
        }
    }
}

/// 通过 where.exe 主动解析 PATH 中的 claude，拿到具体文件路径。
/// 可能命中多行（exe / cmd / ps1 各一行）：优先 exe，其次 cmd/bat
fn resolve_from_path() -> Option<PathBuf> {
    let mut cmd = std::process::Command::new("where.exe");
    cmd.arg("claude");
    #[cfg(windows)]
    {
        use std::os::windows::process::CommandExt;
        cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW
    }
    let out = cmd.output().ok()?;
    if !out.status.success() {
        return None;
    }
    let text = String::from_utf8_lossy(&out.stdout);
    let mut shim: Option<PathBuf> = None;
    for line in text.lines() {
        let p = PathBuf::from(line.trim());
        let ext = p
            .extension()
            .and_then(|e| e.to_str())
            .map(|e| e.to_ascii_lowercase())
            .unwrap_or_default();
        match ext.as_str() {
            "exe" => return Some(p),
            "cmd" | "bat" if shim.is_none() => shim = Some(p),
            _ => {}
        }
    }
    shim
}

/// 常见安装位置：npm 全局安装 → 原生安装器
fn known_candidates() -> Vec<PathBuf> {
    let mut candidates: Vec<PathBuf> = Vec::new();
    if let Ok(appdata) = std::env::var("APPDATA") {
        candidates.push(
            PathBuf::from(appdata)
                .join("npm")
                .join("node_modules")
                .join("@anthropic-ai")
                .join("claude-code")
                .join("bin")
                .join("claude.exe"),
        );
    }
    if let Some(home) = dirs::home_dir() {
        candidates.push(home.join(".local").join("bin").join("claude.exe"));
    }
    candidates
}

/// 解析优先级：手动指定 → PATH 主动解析 → 常见安装位置 → cmd /c 兜底
fn resolve_with_source(custom: &str) -> (ClaudeLauncher, &'static str) {
    let custom = custom.trim();
    if !custom.is_empty() {
        let p = Path::new(custom);
        if p.is_file() {
            return (launcher_for(p), "manual");
        }
        // 手动路径已失效：继续自动检测，设置页据 source 提示用户
    }
    if let Some(p) = resolve_from_path() {
        return (launcher_for(&p), "path");
    }
    for file in known_candidates() {
        if file.is_file() {
            return (launcher_for(&file), "known");
        }
    }
    (
        ClaudeLauncher {
            file: "cmd.exe".into(),
            args_prefix: vec!["/c".into(), "claude".into()],
        },
        "fallback",
    )
}

pub fn resolve_claude_launcher(custom: &str) -> ClaudeLauncher {
    resolve_with_source(custom).0
}

pub fn describe(custom: &str) -> ClaudePathInfo {
    let (l, source) = resolve_with_source(custom);
    let source = if !custom.trim().is_empty() && source != "manual" {
        "manual-invalid"
    } else {
        source
    };
    // cmd /c 间接执行时展示真正的目标（.cmd 路径或兜底的 "claude"）
    let display = match l.args_prefix.last() {
        Some(target) if l.file == "cmd.exe" => target.clone(),
        _ => l.file,
    };
    ClaudePathInfo {
        display,
        source: source.into(),
    }
}

/// 用解析出的启动方式运行 `claude update`，手动更新 CLI。
/// 返回输出文本或失败原因；若仍有其它进程占用 claude.exe，会得到 "in use" 类错误。
pub fn update(custom: &str) -> Result<String, String> {
    let l = resolve_claude_launcher(custom);
    let mut cmd = std::process::Command::new(&l.file);
    for a in &l.args_prefix {
        cmd.arg(a);
    }
    cmd.arg("update");
    #[cfg(windows)]
    {
        use std::os::windows::process::CommandExt;
        cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW
    }
    let out = cmd.output().map_err(|e| format!("无法启动: {e}"))?;
    let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string();
    let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string();
    if out.status.success() {
        let msg = if stdout.is_empty() { stderr } else { stdout };
        Ok(if msg.is_empty() { "更新完成".into() } else { msg })
    } else {
        let msg = if stderr.is_empty() { stdout } else { stderr };
        Err(if msg.is_empty() {
            format!("更新失败，退出码 {:?}", out.status.code())
        } else {
            msg
        })
    }
}

/// 用解析出的启动方式跑一次 `claude --version`，验证当前配置是否可用
pub fn test(custom: &str) -> Result<String, String> {
    let l = resolve_claude_launcher(custom);
    let mut cmd = std::process::Command::new(&l.file);
    for a in &l.args_prefix {
        cmd.arg(a);
    }
    cmd.arg("--version");
    #[cfg(windows)]
    {
        use std::os::windows::process::CommandExt;
        cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW
    }
    let out = cmd.output().map_err(|e| format!("无法启动: {e}"))?;
    if out.status.success() {
        Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
    } else {
        let err = String::from_utf8_lossy(&out.stderr).trim().to_string();
        if err.is_empty() {
            Err(format!("执行失败，退出码 {:?}", out.status.code()))
        } else {
            Err(err)
        }
    }
}
