//! 工具调用审计 + pending 审批恢复 + 工具调用处理

use std::collections::{HashMap, HashSet};
use std::sync::Arc;

use serde::Serialize;
use tauri::{AppHandle, Emitter, State};

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

use crate::state::AppState;

use crate::commands::err_str;

use super::{AiChatEvent, AiSession, PendingApproval, ToolCallDraft};
// tool_registry::generate_diff 经 audit/diff.rs 直接 use（build_write_file_diff 内调用），
// 本文件不再裸名用 generate_diff，故不再在此 use（避免 unused import warning）。

// utils(audit/utils.rs):RiskLevel ↔ 字符串转换 + 字符串安全截断纯 helper。
// 第五批从本文件抽离,行为零变更。pub(crate) use 保持 finalize / restore 子模块
// super::risk_str / super::risk_from_str 引用路径不变;truncate_chars 仅供本文件 DTO 装配裸名用。
mod utils;
pub(crate) use utils::{risk_from_str, risk_str};
use utils::truncate_chars;

// diff(audit/diff.rs):AE-2025-03 write_file 审批预览 diff 生成。
// 第五批从本文件抽离,行为零变更。use 保持本文件 process_tool_calls 裸名调用 build_write_file_diff。
// generate_diff 经 `use super::generate_diff` 引入供 diff.rs 复用。
mod diff;
use diff::build_write_file_diff;

// ============================================================
// 审批历史查询 IPC（AE-2025-08）
// ============================================================

/// 审批历史 DTO(传给前端的精简视图,敏感字段截断防泄露)
///
/// arguments/result 在落库时是完整 JSON(可能含项目名/路径/长结果),审计面板只展示摘要,
/// 故截断到固定长度(参数 120 / 结果 160),既保留可读性又不泄露全量数据到前端 DOM。
#[derive(Debug, Clone, Serialize)]
pub struct ToolExecutionDto {
    pub id: String,
    pub conversation_id: Option<String>,
    pub tool_call_id: String,
    pub tool_name: String,
    /// 参数摘要(截断 120 字符,完整原值仍留库)
    pub arguments_brief: String,
    /// 结果摘要(截断 160 字符,None → 空串便于前端展示)
    pub result_brief: Option<String>,
    /// pending/approved/rejected/executing/completed/failed
    pub status: String,
    /// low/medium/high
    pub risk_level: String,
    pub requested_at: String,
    pub executed_at: Option<String>,
    /// human/auto,None 表示尚未决策
    pub decided_by: Option<String>,
}

/// 审批历史面板查询:按 requested_at 倒序(最新在前)分页返回工具调用审计记录。
///
/// 默认 limit=50 / offset=0(第一页)。limit 在 storage 层钳制 ≤200 防滥用。
/// 敏感字段(arguments/result)截断成摘要返回,完整原值仍留库。
#[tauri::command]
pub async fn list_tool_executions(
    state: State<'_, AppState>,
    limit: Option<u32>,
    offset: Option<u32>,
) -> Result<Vec<ToolExecutionDto>, String> {
    let limit = limit.unwrap_or(50);
    let offset = offset.unwrap_or(0);
    let records = state
        .ai_tool_executions
        .list_recent(limit, offset)
        .await
        .map_err(err_str)?;
    Ok(records
        .into_iter()
        .map(|r| ToolExecutionDto {
            id: r.id,
            conversation_id: r.conversation_id,
            tool_call_id: r.tool_call_id,
            tool_name: r.tool_name,
            arguments_brief: truncate_chars(&r.arguments, 120),
            result_brief: r.result.map(|s| truncate_chars(&s, 160)),
            status: r.status,
            risk_level: r.risk_level,
            requested_at: r.requested_at,
            executed_at: r.executed_at,
            decided_by: r.decided_by,
        })
        .collect())
}


// reason 拼装(resolve_project_label / resolve_task_label / build_approval_reason)
// 拆至子模块 audit/reason.rs(第一批 helper 抽离,行为零变更)。
mod reason;

// process_tool_calls 调用 build_approval_reason,从子模块引入(super 可见,fn 为 pub(super))。
use reason::build_approval_reason;

// restore(audit/restore.rs):启动恢复重建 pending_approvals。
// 第二批从本文件抽离,行为零变更。re-export 保持 commands::ai::audit::* 路径透明。
mod restore;
pub use restore::restore_pending_approvals;

// finalize(audit/finalize.rs):audit_tool_call 写入 + audit_finalize 审批后状态回填。
// 第二批从本文件抽离,行为零变更。pub(crate) use 供本文件 process_tool_calls /
// find_cached_high_risk_result 裸名调用,且保持 commands::ai::audit::* 对 crate 内可见。
mod finalize;
pub(crate) use finalize::{audit_finalize, audit_tool_call};

// cache(audit/cache.rs):F-260616-05 高危工具去重缓存。
// 第三批从本文件抽离,行为零变更。pub(super) use 供本文件 process_tool_calls 裸名调用
// (find_cached_high_risk_result),PENDING_APPROVAL_PLACEHOLDER 供 process_tool_calls 占位回填共享。
mod cache;
pub(super) use cache::{find_cached_high_risk_result, PENDING_APPROVAL_PLACEHOLDER};

// data_change(audit/data_change.rs):AR-11 数据变更联动刷新。
// 第四批从本文件抽离,行为零变更。pub(crate) use 保持 emit_data_changed 对 crate 内可见
// (chat.rs 通过 commands::ai::audit::emit_data_changed 引入并调用),data_change_for_tool 私有。
mod data_change;
pub(crate) use data_change::emit_data_changed;

/// 处理流式接收的工具调用：Low 风险并行执行（join_all），Med/High 进审批门控
/// 返回待审批的工具数量（0 = 全部自动执行完成）
pub(crate) async fn process_tool_calls(
    session: &mut AiSession,
    tool_calls_acc: HashMap<u32, ToolCallDraft>,
    tools_arc: &Arc<AiToolRegistry>,
    db: &Arc<Database>,
    app_handle: &AppHandle,
    conv_id: &str,
) -> usize {
    let mut tc_list: Vec<_> = tool_calls_acc.into_iter().collect();
    tc_list.sort_unstable_by_key(|(i, _)| *i);
    // B-260616-21 治本兜底:LLM 异常复用同 tool_use.id(stream_recv 按 content_block index 分桶,
    // 同 id 不同 index draft 可并存 → 每 draft emit AiToolCallStarted 致同 id emit 两次 → 前端 push 两卡,
    // Completed 按 id 只 update 首张 → 次张残留 running 0行)。process 层按 id 去重——同 id 保留
    // 最小 index 的首个,丢弃后续,保证 emit Started 的 id 唯一。前端 useAiEvents.ts:205 findToolCall
    // 守卫双保险。详 docs/02-架构设计/已编号方案/B-260616-21排查方案-2026-06-16.md。
    let mut seen_ids: HashSet<String> = HashSet::new();
    tc_list.retain(|(_, draft)| seen_ids.insert(draft.id.clone()));
    let mut pending_count = 0usize;
    let audit_repo = AiToolExecutionRepo::new(db);

    // 解析 args + 批量发 Started（前端骨架按原始 index 顺序展示）
    let drafts: Vec<(u32, ToolCallDraft, serde_json::Value)> = tc_list.into_iter()
        .map(|(idx, draft)| {
            let args = serde_json::from_str(&draft.args).unwrap_or(serde_json::Value::Object(Default::default()));
            let _ = app_handle.emit("ai-chat-event", AiChatEvent::AiToolCallStarted {
                id: draft.id.clone(),
                name: draft.name.clone(),
                args: args.clone(),
                conversation_id: Some(conv_id.to_string()),
            });
            (idx, draft, args)
        })
        .collect();

    // 分类：Low 收集并行执行，Med/High 立即进审批门控（push 占位 tool_result）
    //
    // F-260616-05：High risk 在进审批门前先查去重缓存（find_cached_high_risk_result）。
    // 若 LLM 重试同命令（同 tool_name + 同 args，键序无关），命中已落定的旧 tool_result，
    // 把缓存结果作为新 tool_call_id 的 tool_result 回传 LLM，跳过 insert pending + 跳过审批，
    // 断「超时→重试→重新审批」循环。Med 不去重（去重易误伤），Low 无审批本就不进此分支。
    let mut low_risk: Vec<(ToolCallDraft, serde_json::Value)> = Vec::new();
    // trust_hits 收集：AE-04 会话信任命中（同会话已批准同类操作），真实执行但移到锁外 spawn
    // （对齐 Low risk L673-720 不持锁模式）。命中时此处只 emit toast + 收集，不 .await execute。
    // 元组携带 risk_level: 回填审计需原等级(Med/High 才进此分支),避免闭包内再查 registry
    let mut trust_hits: Vec<(ToolCallDraft, serde_json::Value, String, RiskLevel)> = Vec::new();
    for (_, draft, args) in drafts {
        let risk_level = tools_arc.get(&draft.name).map(|t| t.risk_level).unwrap_or(RiskLevel::High);
        match risk_level {
            RiskLevel::Low => low_risk.push((draft, args)),
            RiskLevel::Medium | RiskLevel::High => {
                // AE-2025-04 会话级信任（Session Trust）：首批 write_file / run_command，
                // 同会话已批准过同工具+同目录 → TrustKey 命中 → 自动放行（跳过 pending + 二次确认）。
                // 命中后走与 F-05 去重命中相似的「直接执行 + Completed + 审计 decided_by=auto_trust」路径，
                // 但与 F-05 不同：F-05 复用缓存 tool_result 跳过执行；trust 放行**真实执行工具**
                // （用户信任同目录同类操作，但仍要看每次的真实结果）。
                let trust_hit = super::trust_key_for(&draft.name, &args)
                    .and_then(|key| {
                        // F-260616-09 B 批2:读 per_conv.session_trust(conv_id 来源:本函数入参)。
                        if session.conv_read(conv_id).map(|c| c.session_trust.contains(&key)).unwrap_or(false) {
                            Some(key)
                        } else {
                            None
                        }
                    });
                if let Some(key) = trust_hit {
                    let dir_label = match &key {
                        super::TrustKey::Write { dir } | super::TrustKey::Execute { dir } => dir.clone(),
                    };
                    tracing::info!(
                        tool = %draft.name,
                        dir = %dir_label,
                        new_tool_call_id = %draft.id,
                        "[AE-2025-04] 会话信任命中: 同会话已批准同类操作,自动放行(跳过审批+二次确认)"
                    );
                    // emit 轻量 toast 事件(前端 AiChat.vue 显示"🔓 自动放行: tool(dir)")
                    // toast 即时反馈,锁内 emit 不阻塞
                    let _ = app_handle.emit("ai-chat-event", AiChatEvent::AiToolAutoApproved {
                        id: draft.id.clone(),
                        tool: draft.name.clone(),
                        dir: dir_label.clone(),
                        conversation_id: Some(conv_id.to_string()),
                    });
                    // 收集后循环外 spawn 执行,对齐 Low risk 不持锁(原注释 L593 声称"锁外"但代码持锁,
                    // run_command 慢命令会阻塞同会话所有触 state.ai_session 的 IPC,CR-51 修此)
                    trust_hits.push((draft, args, dir_label, risk_level));
                    continue;
                }
                // F-05：仅 High 查去重缓存；Med 保持原审批流程
                if matches!(risk_level, RiskLevel::High) {
                    if let Some((cached, status)) = find_cached_high_risk_result(session, 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 重试同命令,复用缓存结果跳过审批(断循环)"
                        );
                        // F-260616-09 B 批2:写 per_conv.messages(conv_id 来源:本函数入参)。
                        session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, &cached));
                        let _ = app_handle.emit("ai-chat-event", AiChatEvent::AiToolCallCompleted {
                            id: draft.id.clone(),
                            result: serde_json::Value::String(cached.clone()),
                            conversation_id: Some(conv_id.to_string()),
                        });
                        // 审计:去重命中记一条(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")).await;
                        continue;
                    }
                }
                pending_count += 1;
                // AE-2025-03（路径 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
                };
                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,
                    diff: approval_diff.clone(),
                });
                session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, PENDING_APPROVAL_PLACEHOLDER));
                let reason = build_approval_reason(&draft.name, &args, risk_level, db).await;
                let _ = app_handle.emit("ai-chat-event", AiChatEvent::AiApprovalRequired {
                    id: draft.id.clone(),
                    name: draft.name.clone(),
                    args: args.clone(),
                    reason,
                    diff: approval_diff,
                    conversation_id: Some(conv_id.to_string()),
                });
                audit_tool_call(&audit_repo, conv_id, &draft.id, &draft.name, &draft.args, "pending", risk_level, None, None).await;
            }
        }
    }

    // AE-04 trust-hit 并行执行：execute + 即时 emit 在闭包内（闭包不访问 session，非"锁已释放"——
    // session 锁仍由调用方 agentic.rs 持有至 process_tool_calls 返回，CR-53 审查纠正原"移锁外"误述），
    // push tool_result / audit 在 join_all 后串行回填（持锁）。对齐 Low risk 并行模式。
    // CR-51 修：原 inline .await execute 串行执行每个工具（阻塞期间锁被持有，run_command 慢命令
    // 阻塞同会话 IPC）；改 join_all 并行多工具减少总阻塞时间（锁持有时长不变，并行化降阻塞）。
    // join_all 保序——结果顺序 = trust_hits 输入顺序 = tc_list 原始 index 顺序，不额外 sort
    if !trust_hits.is_empty() {
        let results: Vec<(ToolCallDraft, RiskLevel, Result<String, String>)> =
            futures::future::join_all(trust_hits.into_iter().map(|(draft, args, _dir_label, risk_level)| {
                let tools = tools_arc.clone();
                let app_clone = app_handle.clone();
                let conv_clone = conv_id.to_string();
                async move {
                    let exec_result = tools.execute(&draft.name, args).await;
                    match exec_result {
                        Ok(val) => {
                            let content = val.to_string();
                            let _ = app_clone.emit("ai-chat-event", AiChatEvent::AiToolCallCompleted {
                                id: draft.id.clone(),
                                result: serde_json::Value::String(content.clone()),
                                conversation_id: Some(conv_clone),
                            });
                            // AR-11：数据变更类工具执行成功后 emit df-data-changed 联动刷新
                            // (write_file/run_command 不在 data_change_for_tool 映射内,emit_data_changed 内部 None 即 noop)
                            emit_data_changed(&app_clone, &draft.name);
                            (draft, risk_level, Ok(content))
                        }
                        Err(e) => {
                            let content = e.to_string();
                            let _ = app_clone.emit("ai-chat-event", AiChatEvent::AiToolCallCompleted {
                                id: draft.id.clone(),
                                result: serde_json::Value::String(content.clone()),
                                conversation_id: Some(conv_clone),
                            });
                            (draft, risk_level, Err(content))
                        }
                    }
                }
            })).await;

        // 串行回填 tool_result + 审计（持 session 锁）
        for (draft, risk_level, outcome) in results {
            let (status, content) = match outcome {
                Ok(c) => ("completed", c),
                Err(c) => ("failed", c),
            };
            // F-260616-09 B 批2:写 per_conv.messages。
            session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, content.clone()));
            // 审计:trust 放行仍记一条(decided_by=auto_trust),留痕可追溯
            audit_tool_call(&audit_repo, conv_id, &draft.id, &draft.name, &draft.args, status, risk_level, Some(content), Some("auto_trust")).await;
        }
    }

    // Low 风险并行执行：execute + 即时 emit 在闭包内（不持 session 锁），
    // push tool_result / audit 在 join_all 后串行回填（持锁，与 Med/High 占位拼接）。
    // join_all 保序——结果顺序 = low_risk 输入顺序 = tc_list 原始 index 顺序，不额外 sort
    if !low_risk.is_empty() {
        let results: Vec<(ToolCallDraft, Result<String, String>)> =
            futures::future::join_all(low_risk.into_iter().map(|(draft, args)| {
                let tools = tools_arc.clone();
                let app_clone = app_handle.clone();
                let conv_clone = conv_id.to_string();
                async move {
                    let result = tools.execute(&draft.name, args).await;
                    match result {
                        Ok(val) => {
                            let _ = app_clone.emit("ai-chat-event", AiChatEvent::AiToolCallCompleted {
                                id: draft.id.clone(),
                                result: val.clone(),
                                conversation_id: Some(conv_clone),
                            });
                            // AR-11（方案A）：数据变更工具执行成功后 emit df-data-changed,
                            // 前端 store listen 刷新列表（仅命中映射的工具 emit，见 emit_data_changed）。
                            emit_data_changed(&app_clone, &draft.name);
                            (draft, Ok(val.to_string()))
                        }
                        Err(e) => {
                            // AR-6（定向B）：Low 工具失败不 emit AiError。
                            // 原逻辑 emit AiError 会令前端置 streaming=false + 错误气泡，但 process_tool_calls
                            // 仍返回 pending_count=0，agentic loop 续下一轮 → 前端 false 后端跑，状态紊乱。
                            // 现改为正常 emit AiToolCallCompleted（result=错误信息），错误包进 tool_result
                            // 让 LLM 看到工具失败自行决定下一步，loop 正常续，前端状态一致。
                            let err_msg = format!("工具 {} 执行失败: {}", draft.name, e);
                            let _ = app_clone.emit("ai-chat-event", AiChatEvent::AiToolCallCompleted {
                                id: draft.id.clone(),
                                result: serde_json::Value::String(err_msg.clone()),
                                conversation_id: Some(conv_clone),
                            });
                            (draft, Err(err_msg))
                        }
                    }
                }
            })).await;

        // 串行回填 tool_result + 审计（持 session 锁）
        for (draft, outcome) in results {
            let (status, content) = match outcome {
                Ok(c) => ("completed", c),
                Err(c) => ("failed", c),
            };
            // F-260616-09 B 批2:写 per_conv.messages。
            session.conv(conv_id).messages.push(ChatMessage::tool_result(&draft.id, content.clone()));
            audit_tool_call(&audit_repo, conv_id, &draft.id, &draft.name, &draft.args, status, RiskLevel::Low, Some(content), Some("auto")).await;
        }
    }

    pending_count
}
