//! 审批门控逻辑。
//!
//! 审批策略(方案 B:Persona 维度 + 扩展点):
//! - reviewer/analyst 人设的工具调用全 auto(只读/分析,审批无意义)
//! - 其他人设走现有 auto_exec_mode 三档
//! - Phase 2 扩展点:可在 should_auto_for_persona 内加工具维度 if 分支

use std::sync::Arc;

use tauri::{AppHandle, Emitter, Manager};
use tokio::sync::Mutex;

use df_ai::ai_tools::{AiToolRegistry, RiskLevel};
use df_ai::provider::ChatMessage;
use df_storage::crud::AiToolExecutionRepo;
use df_storage::db::Database;

use super::cache::{find_cached_high_risk_result, pending_placeholder_for};
use super::diff::build_write_file_diff;
use super::reason::build_approval_reason;
use super::record::audit_tool_call;
use super::super::{AiChatEvent, AiSession, ApprovalKind, PendingApproval, ToolCallDraft, is_destructive_command, trust_key_for, TrustKey};

/// 按 risk_level + auto_exec_mode 判定是否自动执行。
///
/// F-#97 自动执行范围三档（2026-06-22）：low/medium/all，默认 low。
/// - low：仅 Low 自动（等价旧行为）
/// - medium：Low+Medium 自动
/// - all：全自动无审批（完全 AI 接管）
///
/// C-260627：patch_file 小改动（≤5 行）特例自动放行，不阻塞 AI 工作流。
pub(super) fn classify_risk_and_auto(
    risk_level: RiskLevel,
    auto_exec_mode: &str,
    tool_name: &str,
    args: &serde_json::Value,
) -> bool {
    let _ = args; // 预留扩展点(未来可加路径白名单/文件类型等维度)
    // patch_file 是 AI 编码主力工具,改文件是 AI Coding 的工作常态。
    // 原本“小改动(≤5 行)豁免、大改动审批”的设计在实际使用中频繁打断 AI 工作流
    // (AI 改文件往往远超 5 行),且文件已写入会落 audit 表可追溯,审批价值低。
    // 决策:patch_file 全档位自动放行(low/medium/all 均不审批),与 read_file 同级处理。
    if tool_name == "patch_file" {
        return true;
    }
    match risk_level {
        RiskLevel::Low => true,
        RiskLevel::Medium => auto_exec_mode == "medium" || auto_exec_mode == "all",
        RiskLevel::High => auto_exec_mode == "all",
    }
}

/// 审批策略方案 B:Persona 维度判定。
///
/// 在 classify_risk_and_auto 之上叠加人设维度:
/// - reviewer/analyst 人设:工具白名单已限制为只读,全部 auto(审批纯浪费)
/// - 其他人设:走 classify_risk_and_auto 原逻辑
///
/// Phase 2 扩展点:在此函数内加工具维度 if 分支,例如:
/// ```ignore
/// if persona_id == "coder" && tool_name == "write_file" {
///     if let Some(path) = args.get("path").and_then(|v| v.as_str()) {
///         if path.ends_with(".md") { return true; } // 写文档 auto
///     }
/// }
/// ```
#[allow(dead_code)] // 接线到 process_tool_calls 需要 SubTask 携带 persona_id,后续批次接入
pub(super) fn should_auto_for_persona(
    persona_id: Option<&str>,
    risk_level: RiskLevel,
    auto_exec_mode: &str,
    tool_name: &str,
    args: &serde_json::Value,
) -> bool {
    // 人设维度:reviewer/analyst 全 auto
    match persona_id {
        Some("reviewer") | Some("analyst") => {
            tracing::debug!(
                persona = ?persona_id,
                tool = %tool_name,
                "[POLICY-B] 只读人设全 auto"
            );
            return true;
        }
        _ => {}
    }
    // 兜底:现有三档 + patch_file 特例
    classify_risk_and_auto(risk_level, auto_exec_mode, tool_name, args)
}

/// 检查会话信任命中。
///
/// 首批信任工具：write_file / run_command。同会话已批准过同工具+同目录 →
/// `TrustKey` 命中，返回 `Some(TrustKey)`；否则返回 `None`（走原审批流程）。
///
/// 签名改 `session_arc: &Arc<Mutex<AiSession>>`,内部短 lock 读
/// `session_trust` 后立即 drop,信任查询不持锁,与 process_tool_calls 持锁 await 反模式解耦。
pub(super) async fn check_trust_hits(
    draft: &ToolCallDraft,
    args: &serde_json::Value,
    session_arc: &Arc<Mutex<AiSession>>,
    conv_id: &str,
) -> Option<TrustKey> {
    let key = trust_key_for(&draft.name, args)?;
    // 安全门控（run_command 会话信任按目录不按命令的补丁）：
    // run_command 的信任 key 只用 working_dir，一次批准 → 同目录任意命令 auto。
    // 破坏性命令（rm/del/format/... 不可逆）绝不 auto 放行，即使同目录已批准过，
    // 也必须每轮走正常审批。命中黑名单直接返 None，让流程继续走 pending 审批。
    if matches!(key, TrustKey::Execute { .. }) {
        if let Some(cmd) = args.get("command").and_then(|v| v.as_str()) {
            if is_destructive_command(cmd) {
                tracing::info!(
                    tool = %draft.name,
                    new_tool_call_id = %draft.id,
                    "[会话信任] 命中破坏性命令黑名单，跳过 auto 放行（走正常审批）"
                );
                return None;
            }
        }
    }
    // 短 lock 读 session_trust(仅 contains 判定,无 await,纳秒级),命中即返 key
    let hit = {
        let session = session_arc.lock().await;
        session.conv_read(conv_id)
            .map(|c| c.session_trust.contains(&key))
            .unwrap_or(false)
    };
    if hit { Some(key) } else { None }
}

/// 插入 pending 审批：生成 diff → 重试检测 → insert PendingApproval + 占位 tool_result
/// → 拼 reason + emit AiApprovalRequired + 审计落 pending 纪录。
///
/// **注意**：调用方应在调用前先 +=1 `pending_count`（保持与原 `handle_approval_tool`
/// 行为一致——重试 skip 分支也在 `pending_count += 1` 之后返回）。
///
/// 签名改 `session_arc: &Arc<Mutex<AiSession>>`,所有慢操作
/// (build_write_file_diff/detect_retry_count/build_approval_reason/audit_tool_call)
/// 在锁外 await,仅 `pending_approvals.insert` + `messages.push` 两处纯写改短 lock 段。
pub(super) async fn insert_pending_approval(
    draft: ToolCallDraft,
    args: serde_json::Value,
    risk_level: RiskLevel,
    session_arc: &Arc<Mutex<AiSession>>,
    conv_id: &str,
    audit_repo: &AiToolExecutionRepo,
    app_handle: &AppHandle,
    db: &Arc<Database>,
    current_message_id: Option<&str>,
) {
    // （路径 B）：write_file 挂起审批前预读旧文件生成 diff。
    // 仅 write_file（覆盖整文件，有完整新旧内容可对比）；其他工具 diff=None。
    // 旧文件不存在（新建）→ diff=None，前端回退显新 content。
    // 读失败不阻断审批（容错：文件无读权限等极端情况降级为无 diff 预览）。
    let approval_diff: Option<String> = if draft.name == "write_file" {
        build_write_file_diff(&args).await
    } else {
        None
    };

    // (容错/恢复，开关 df-ai-approval-retry)：同 tc_id 重试检测。
    // 与 High risk 的 find_cached_high_risk_result 互补：去重按 (tool_name,args) 匹配
    // （High only），本 guard 按 tc_id 匹配（覆盖 Med + High 残留场景）。
    // 同 tc_id 已有审计落定记录 → retry_count≥1，跳过审批 + emit Completed，断死循环。
    // 兜底：flag 关或无审计记录 → retry_count=0，等价原行为。
    let retry_count = detect_retry_count(audit_repo, &draft.id).await;
    if retry_count >= 1 {
        let skip_msg = format!(
            "已跳过重试（同 tool_call_id={} 此前已审批执行过，防 LLM 死循环重试同卡死工具）",
            draft.id
        );
        // 短 lock 段:push tool_result(纯写,无 await)
        {
            let mut session = session_arc.lock().await;
            session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &skip_msg));
        }
        // L3 emit 双写：重试 guard 跳过 emit Completed 双路发布。
        let ev = AiChatEvent::AiToolCallCompleted {
            id: draft.id.clone(),
            result: serde_json::Value::String(skip_msg.clone()),
            conversation_id: Some(conv_id.to_string()),
        };
        let _ = app_handle.emit("ai-chat-event", ev.clone());
        let _ = app_handle.state::<crate::state::AppState>().ai_event_bus.publish_event(ev);
        audit_tool_call(audit_repo, conv_id, &draft.id, &draft.name, &draft.args, "skipped_retry", risk_level, Some(skip_msg), Some("auto_retry_guard"), current_message_id).await;
        return;
    }

    // 短 lock 段:纯写 pending_approvals.insert + 占位 tool_result push
    {
        let mut session = session_arc.lock().await;
        session.pending_approvals.insert(draft.id.clone(), PendingApproval {
            tool_call_id: draft.id.clone(),
            tool_name: draft.name.clone(),
            arguments: args.clone(),
            conversation_id: Some(conv_id.to_string()),
            recovered: false,
            // 普通风险审批标 kind=Risk{diff}（下沉原 diff 字段）。
            kind: ApprovalKind::Risk { diff: approval_diff.clone() },
            retry_count,
            created_at: Some(std::time::SystemTime::now()),
        });
        // 占位带 __PENDING__:tc_id 标记，供 sanitize 豁免保留 + 出口断言自愈（防 400 orphan）
        session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &pending_placeholder_for(&draft.id)));
    }
    // 慢操作锁外:拼 reason(DB 读)+ emit + 审计落 pending 纪录(DB 写)
    let reason = build_approval_reason(&draft.name, &args, risk_level, db).await;
    // L3 emit 双写：Med/High 风险审批挂起 AiApprovalRequired 双路发布（tunnel 透传 miniapp 弹审批窗）。
    let ev = AiChatEvent::AiApprovalRequired {
        id: draft.id.clone(),
        name: draft.name.clone(),
        args: args.clone(),
        reason,
        diff: approval_diff,
        conversation_id: Some(conv_id.to_string()),
    };
    let _ = app_handle.emit("ai-chat-event", ev.clone());
    let _ = app_handle.state::<crate::state::AppState>().ai_event_bus.publish_event(ev);
    audit_tool_call(audit_repo, conv_id, &draft.id, &draft.name, &draft.args, "pending", risk_level, None, None, current_message_id).await;
}

/// (容错/恢复，开关 `df-ai-approval-retry`)：查审计表推算同 tc_id 重试计数。
///
/// 返回语义：
/// - 0：审计表无该 tc_id 落定记录（或仅 pending），属首次审批执行，正常挂起。
/// - ≥1：审计表已有该 tc_id 的落定记录（executed/failed/rejected/skipped_retry），即该
///   tc_id 此前已被审批执行过一次，LLM 又用同 id 重试 → 调用方据 ≥1 跳过执行 + emit Completed，
///   断「超时/权限错→LLM 死循环重试同 id→重新挂起→用户被迫二次授权」循环。
///
/// 实现：查 `find_by_tool_call_id`，status 为 pending 视为"尚未落定"（返 0，首次挂起审批的
/// 正常态）；其余落定状态返 1。retry_count 当前仅取 0/1（断路器语义：第二次即跳过），
/// 字段类型 u32 留给未来"允许多次重试"扩展（配置上限阈值）。
///
/// 兜底/回退：flag 关（文档标记）或审计查询失败 → 返 0，等价原行为（单次审批执行，无重试防护）。
pub(super) async fn detect_retry_count(audit_repo: &AiToolExecutionRepo, tc_id: &str) -> u32 {
    // 审计查询失败不阻断主流程（DB 故障等降级为无重试防护，返回 0 走原审批流程）
    let rec = match audit_repo.find_by_tool_call_id(tc_id).await {
        Ok(opt) => match opt {
            Some(r) => r,
            None => return 0, // 无记录 = 首次
        },
        Err(e) => {
            tracing::warn!("[approval-retry] 查审计表 tc_id={} 失败（降级无重试防护）: {}", tc_id, e);
            return 0;
        }
    };
    // pending = 首次挂起审批（尚未落定）；其余落定状态 = 已执行过 → 计 1 次重试
    if rec.status == "pending" {
        0
    } else {
        1
    }
}

/// 对单条工具调用执行审批门控决策：
///
/// 1. 按 `auto_exec_mode`（low/medium/all） + `risk_level` + `patch_file` 小改动特例
///    判定是否应自动执行（`classify_risk_and_auto`）。若是 → 推入 `low_risk` 返回。
/// 2. 否则走审批分支：会话信任（`check_trust_hits`）→ 高危去重缓存 → 重试 guard →
///    `insert_pending_approval`（write_file diff + 挂起 + emit + 审计落 pending 记录）。
///
/// 签名改 `session_arc: &Arc<Mutex<AiSession>>`,所有 session 访问
/// 都改短 lock 段(check_trust_hits/find_cached_high_risk_result 内部短 lock,命中后 push
/// tool_result 短 lock 段)。慢操作(audit_tool_call/find_cached_high_risk_result 的 DB 查)
/// 全在锁外 await,根治 process_tool_calls 持 session lock 期间 await 慢操作死锁反模式。
pub(super) async fn handle_approval_tool(
    draft: ToolCallDraft,
    args: serde_json::Value,
    risk_level: RiskLevel,
    auto_exec_mode: &str,
    session_arc: &Arc<Mutex<AiSession>>,
    conv_id: &str,
    _tools_arc: &Arc<AiToolRegistry>,
    audit_repo: &AiToolExecutionRepo,
    app_handle: &AppHandle,
    db: &Arc<Database>,
    low_risk: &mut Vec<(ToolCallDraft, serde_json::Value, RiskLevel)>,
    trust_hits: &mut Vec<(ToolCallDraft, serde_json::Value, String, RiskLevel)>,
    pending_count: &mut usize,
    current_message_id: Option<&str>,
) {
    // ── Step 1: 自动执行判定 ──
    if classify_risk_and_auto(risk_level, auto_exec_mode, &draft.name, &args) {
        low_risk.push((draft, args, risk_level));
        return;
    }

    // ── Step 2: 会话信任检查 ──
    // 会话级信任（Session Trust）：首批 write_file / run_command，
    // 同会话已批准过同工具+同目录 → TrustKey 命中 → 自动放行（跳过 pending + 二次确认）。
    // 命中后走与去重命中相似的「直接执行 + Completed + 审计 decided_by=auto_trust」路径，
    // 但与 F-05 不同：F-05 复用缓存 tool_result 跳过执行；trust 放行**真实执行工具**
    // （用户信任同目录同类操作，但仍要看每次的真实结果）。
    if let Some(key) = check_trust_hits(&draft, &args, session_arc, conv_id).await {
        let dir_label = match &key {
            TrustKey::Write { dir } | TrustKey::Execute { dir } => dir.clone(),
        };
        tracing::info!(
            tool = %draft.name,
            dir = %dir_label,
            new_tool_call_id = %draft.id,
            "[会话信任] 命中: 同会话已批准同类操作，自动放行（跳过审批+二次确认）"
        );
        // emit 轻量 toast 事件（前端 AiChat.vue 显示"🔓 自动放行: tool(dir)"）
        // L3 emit 双写：会话信任自动放行 toast 双路发布（tunnel 透传 miniapp 即时反馈）。
        let ev = AiChatEvent::AiToolAutoApproved {
            id: draft.id.clone(),
            tool: draft.name.clone(),
            dir: dir_label.clone(),
            conversation_id: Some(conv_id.to_string()),
        };
        let _ = app_handle.emit("ai-chat-event", ev.clone());
        let _ = app_handle.state::<crate::state::AppState>().ai_event_bus.publish_event(ev);
        // 收集后循环外 spawn 执行，对齐 Low risk 不持锁（原注释 L593 声称"锁外"但代码持锁，
        // run_command 慢命令会阻塞同会话所有触 state.ai_session 的 IPC，CR-51 修此）
        trust_hits.push((draft, args, dir_label, risk_level));
        return;
    }

    // ── Step 3: F-05 高危去重缓存（仅 High） ──
    if matches!(risk_level, RiskLevel::High) {
        // find_cached_high_risk_result 内部短 lock 读 messages + 锁外 await DB 查 status,
        // 返回 (cached_content, status) 时不持锁
        if let Some((cached, status)) = find_cached_high_risk_result(session_arc, conv_id, audit_repo, &draft.name, &args).await {
            // 命中：把缓存结果作为新 tool_call_id 的 tool_result 回传，跳过审批
            tracing::info!(
                tool = %draft.name,
                new_tool_call_id = %draft.id,
                "[F-05] 高危工具去重命中:LLM 重试同命令，复用缓存结果跳过审批（断循环）"
            );
            // 短 lock 段:push tool_result(纯写,无 await)
            {
                let mut session = session_arc.lock().await;
                session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &cached));
            }
            // L3 emit 双写：高危去重命中复用缓存 emit Completed 双路发布。
            let ev = AiChatEvent::AiToolCallCompleted {
                id: draft.id.clone(),
                result: serde_json::Value::String(cached.clone()),
                conversation_id: Some(conv_id.to_string()),
            };
            let _ = app_handle.emit("ai-chat-event", ev.clone());
            let _ = app_handle.state::<crate::state::AppState>().ai_event_bus.publish_event(ev);
            // 审计：去重命中记一条（status 透传缓存来源 completed/rejected/failed，SW-260618-16；decided_by=auto_dedup），不进 pending
            audit_tool_call(audit_repo, conv_id, &draft.id, &draft.name, &draft.args, &status, risk_level, Some(cached), Some("auto_dedup"), current_message_id).await;
            return;
        }
    }

    // ── Step 4: 插入 pending 审批 ──
    *pending_count += 1;
    insert_pending_approval(
        draft, args, risk_level, session_arc, conv_id, audit_repo, app_handle, db,
        current_message_id,
    ).await;
}
