//! Shell 执行器 — 通过 tokio::process 执行 shell 命令

use serde::{Deserialize, Serialize};
use std::process::Stdio;

/// Shell 命令执行结果
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellResult {
    /// 标准输出
    pub stdout: String,
    /// 标准错误
    pub stderr: String,
    /// 退出码
    pub exit_code: Option<i32>,
    /// 执行耗时（毫秒）
    pub duration_ms: u64,
}

/// Shell 类型选择
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ShellType {
    /// Windows cmd.exe (默认 Windows)
    Cmd,
    /// PowerShell 5.x(Windows 自带,不支持 && 运算符)
    PowerShell,
    /// PowerShell 7(pwsh,支持 && 运算符;运行时探测,未装回退 PowerShell)
    Pwsh,
    /// Unix sh (默认非 Windows)
    Sh,
}

impl Default for ShellType {
    fn default() -> Self {
        // L1 环境感知:Windows 默认 PowerShell 系(非 Cmd)。PowerShell 对引号/$变量/Unicode 处理
        // 远优于 cmd,从根上避 kms 类引号转义地狱(seq26-52 撞墙 20+ 次)。AI 写文件执行见 env_profile。
        // BUG-260623-04:优先 pwsh(PS7,支持 && 运算符)——LLM 训练数据 Unix 多,普遍生成 `cd x && y`,
        // PS5 不支持 && 致命令失败(实测会话 6acb7f9b `cd ... && git init` InvalidEndOfLine)。
        // 探测失败(未装 pwsh)回退 PS5。探测结果 OnceLock 缓存(只探一次)。
        if cfg!(windows) {
            if probe_pwsh() { ShellType::Pwsh } else { ShellType::PowerShell }
        } else {
            ShellType::Sh
        }
    }
}

/// 探测 pwsh(PowerShell 7)是否可用(OnceLock 缓存,只探一次)。
///
/// LLM 普遍生成 `&&`(Unix 习惯),仅 PS7+ 支持,Windows 自带 PS5 不支持。
/// 探测:成功 spawn `pwsh -Command exit 0` 即可用。同步阻塞仅一次(spawn 极快),
/// Windows 加 CREATE_NO_WINDOW 防黑窗闪现。
fn probe_pwsh() -> bool {
    static CACHE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
    *CACHE.get_or_init(|| {
        let mut cmd = std::process::Command::new("pwsh");
        cmd.arg("-NoProfile").arg("-Command").arg("exit 0");
        cmd.stdout(Stdio::null()).stderr(Stdio::null());
        #[cfg(windows)]
        {
            use std::os::windows::process::CommandExt;
            cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW
        }
        cmd.status().map(|s| s.success()).unwrap_or(false)
    })
}

/// Shell 命令执行请求
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ShellRequest {
    /// 要执行的命令
    pub command: String,
    /// 工作目录
    pub working_dir: Option<String>,
    /// 环境变量
    pub env: std::collections::HashMap<String, String>,
    /// 超时时间（秒），None 表示不超时
    pub timeout_secs: Option<u64>,
    /// Shell 类型（默认：Windows→Cmd, 非Windows→Sh）
    #[serde(default)]
    pub shell_type: Option<ShellType>,
}

/// 执行 Shell 命令
///
/// 支持超时(timeout_secs)、环境变量(env)、工作目录(working_dir),
/// kill_on_drop(true) 保证超时后子进程不残留,shell_type 可选 Cmd/PowerShell/Sh。
pub async fn execute(request: ShellRequest) -> anyhow::Result<ShellResult> {
    let start = std::time::Instant::now();

    let shell_type = request.shell_type.unwrap_or_default();
    let mut cmd = match shell_type {
        ShellType::PowerShell => {
            let mut c = tokio::process::Command::new("powershell");
            c.arg("-NoProfile").arg("-Command").arg(&request.command);
            c.stdout(Stdio::piped()).stderr(Stdio::piped());
            c
        }
        ShellType::Pwsh => {
            // PS7(支持 && 运算符),同 PS5 参数语义。-NoProfile 避加载用户 profile(速度+确定性)
            let mut c = tokio::process::Command::new("pwsh");
            c.arg("-NoProfile").arg("-Command").arg(&request.command);
            c.stdout(Stdio::piped()).stderr(Stdio::piped());
            c
        }
        ShellType::Cmd => {
            let mut c = tokio::process::Command::new("cmd");
            c.arg("/C").arg(&request.command);
            c.stdout(Stdio::piped()).stderr(Stdio::piped());
            c
        }
        ShellType::Sh => {
            let mut c = tokio::process::Command::new("sh");
            c.arg("-c").arg(&request.command);
            c.stdout(Stdio::piped()).stderr(Stdio::piped());
            c
        }
    };

    // CR-15-1: kill_on_drop(true) 让 Command 被 drop 时主动 kill 子进程。
    // 配合 tokio::time::timeout 超时场景:超时 drop future → Command 析构 → kill 子进程,
    // 不再让超时后的命令变孤儿继续后台跑(长 hang 命令/死循环仍占资源)。
    // 对齐 tool_registry.rs:514「进程已终止」文案名副其实。tokio 1.52.3 支持。
    cmd.kill_on_drop(true);

    // B-260619-01: Windows 下创建子进程默认弹控制台窗口(cmd/powershell 黑窗闪现)。
    // CREATE_NO_WINDOW(0x0800_0000) 标志抑制窗口创建,后台静默执行。
    // tokio::process::Command 在 Windows 自带 creation_flags 方法(无需 std CommandExt trait)。
    #[cfg(windows)]
    {
        cmd.creation_flags(0x0800_0000);
    }

    if let Some(dir) = &request.working_dir {
        cmd.current_dir(dir);
    }

    for (key, value) in &request.env {
        cmd.env(key, value);
    }

    let output = match request.timeout_secs {
        Some(secs) => tokio::time::timeout(
            std::time::Duration::from_secs(secs),
            cmd.output(),
        )
        .await
        .map_err(|_| anyhow::anyhow!("命令执行超时({}s): {}", secs, request.command))??,
        None => cmd.output().await?,
    };

    let duration = start.elapsed().as_millis() as u64;

    Ok(ShellResult {
        stdout: String::from_utf8_lossy(&output.stdout).to_string(),
        stderr: String::from_utf8_lossy(&output.stderr).to_string(),
        exit_code: output.status.code(),
        duration_ms: duration,
    })
}
