//! get_app_config AI 工具 — 查 DevFlow 自身当前生效的 AI 配置(只读 + 脱敏)。
//!
//! ## 治什么症状
//!
//! 实测 AI 想看「当前 provider/model/agent 设置」时,因没原生「查配置」工具,只能 run_command
//! 执行 PowerShell 内联 node/python 脚本查 db(node sqlite/...),命令行引号三重嵌套必失败
//! (PowerShell 单引号包 node -e 内含 JS 模板字符串/双引号/db 路径,转义层数爆炸,复盘实证)。
//! 本工具直接返当前配置 JSON,LLM 一调即得,根治 run_command 绕行。
//!
//! ## 安全(只读 + 脱敏)
//!
//! - **只读**:纯 list_all/load 内存值,无任何写库/写文件副作用。RiskLevel::Low。
//! - **api_key 脱敏**:复用 [`mask_api_key`](crate::commands::ai::commands::mask_api_key)
//!   (前 4 + `••••` + 后 4),迁移后 DB api_key 空列 → 经 keyring 解析真实 key 再脱敏,
//!   绝不向 LLM 回灌明文 key(对齐 ai_list_providers IPC 脱敏口径,单点维护)。
//! - **app_settings 不返敏感 KV**:仅返与 AI 行为直接相关的已知无害 key
//!   (knowledge/timeout/custom_prompt 存在性 + 长度),自定义指令原文不回灌防指令注入。
//!
//! ## 数据来源(全部经 GetAppConfigCtx 捕获句柄,无 AppState 自引用)
//!
//! - providers / default_provider:`AiProviderRepo::new(&ctx.db).list_all()` + keyring 解析
//! - agent:`ctx.agent_max_iterations` 等内存原子量当前 load 值(热改即反映)
//! - app_settings:`SettingsRepo::new(&ctx.db).get_all()` 后白名单筛选
//!
//! ## 为何不直接捕获 &AppState
//!
//! `build_ai_tool_registry` 在 `AppState::init` 内、AppState 构建中被调用(state.rs),
//! 此时无法传 `&AppState`(自引用 + 循环依赖,对齐 resolver 持 Arc<Database> 非 AppState 的
//! 既有设计)。故把所需 Arc 句柄(db + agent 配置原子量 + LlmConcurrency)打包成
//! [`GetAppConfigCtx`],state.rs init 先建这些 Arc(不依赖 db)再 build registry 注入。

use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, AtomicUsize};

use serde_json::{json, Value};

use df_storage::crud::{AiProviderRepo, SettingsRepo};

use crate::state::LlmConcurrency;

/// 复用 IPC 层脱敏规则(单点维护,与 ai_list_providers 同口径)。
use crate::commands::ai::commands::mask_api_key;
/// keyring 异步解析(Tauri 单线程 runtime,同步 keyring 调用会卡)。
use crate::commands::ai::secret::get_provider_secret_async;

/// app_settings KV 白名单(仅这些 key 进 LLM 视图,其余敏感/无关 KV 一律不返)。
///
/// 选取标准:与 AI 行为直接相关 + 无敏感凭证内容。`custom_prompt` 只返长度不返原文
/// (用户可能写入含指令的私有提示,原文回灌进 LLM 上下文有指令注入风险 + 撑爆 prompt)。
const ALLOWED_SETTING_KEYS: &[&str] = &[
    // 知识库行为配置(摘要/提炼开关,JSON 结构;值含开关不含凭证)
    "df-knowledge-config",
    // 审批超时(ms 数值,持久化版,与 AppState.approval_timeout_minutes 互补)
    "df-approval-timeout",
];

/// get_app_config 工具注册期捕获的句柄集合(绕 AppState 自引用)。
///
/// 各字段均为 `Arc`(或内部全 Arc 的 Clone 廉价类型),clone 进工具闭包后与 AppState
/// 字段共享同一底层原子量/Semaphore,故 handler 读到的永远是「当前生效值」
/// (经 ai_set_concurrency_config / ai_set_agent_max_iterations 等 IPC 热改后立即反映)。
///
/// 字段对应 AppState 同名字段,state.rs init 先建这些 Arc 再组装 state(顺序调整,
/// 因 build_ai_tool_registry 在 AppState 构建中被调,无法传 &AppState)。
///
/// `Clone` 廉价(全 Arc 字段):declare_tool! 宏把 ctx clone 进工具闭包,每次工具执行
/// 再 clone 一份进 async move 块(宏展开语义,见 crates/df-ai/src/ai_tools_decl.rs)。
#[derive(Clone)]
pub struct GetAppConfigCtx {
    /// 数据库句柄(读 ai_providers / app_settings)
    pub db: Arc<df_storage::db::Database>,
    /// Agentic 循环最大轮次(热改即生效)
    pub agent_max_iterations: Arc<AtomicUsize>,
    /// 流式对话失败自动重试次数(热改即生效)
    pub agent_max_retries: Arc<AtomicUsize>,
    /// 审批超时分钟数(热改即生效)
    pub approval_timeout_minutes: Arc<AtomicU64>,
    /// LLM 并发控制(内部全 Arc,Clone 廉价,共享同一组 Semaphore)
    pub llm_concurrency: LlmConcurrency,
}

/// 单个 provider 的脱敏视图(无 api_key 字段,绝不向 LLM 暴露任何 key 痕迹)。
///
/// 列表用:返所有 provider 概要(name/type/url/model/是否默认/是否启用),AI 知道有哪些
/// provider 可用,不返 model_configs(详组能力配置,体积大且对「查当前配置」无必要)。
fn provider_summary(p: &df_storage::models::AiProviderRecord) -> Value {
    json!({
        "name": p.name,
        "provider_type": p.provider_type,
        "base_url": p.base_url,
        "default_model": p.default_model,
        "is_default": p.is_default,
        "enabled": p.enabled,
    })
}

/// 默认 provider 的扩展视图(含 api_key_masked,供 AI 判断 key 是否配置/有效)。
///
/// 仅默认这一条返脱敏 key(其他 provider 列表项不带 key 字段),平衡「AI 需知道 key 在不在」
/// 与「最小披露」。空 key → 空串(明确告知 key 缺失,而非误导性地返 mask 占位)。
fn default_provider_view(
    p: &df_storage::models::AiProviderRecord,
    real_key: &str,
) -> Value {
    let mut view = provider_summary(p);
    // 作为对象才能插入字段(provider_summary 返的是 Object)。
    if let Some(obj) = view.as_object_mut() {
        let masked = if real_key.is_empty() {
            String::new()
        } else {
            mask_api_key(real_key)
        };
        obj.insert("api_key_masked".into(), json!(masked));
    }
    view
}

/// get_app_config 工具 handler 入口。
///
/// 返回结构:
/// ```jsonc
/// {
///   "default_provider": { name, provider_type, base_url, default_model, is_default, enabled, api_key_masked: "sk-l••••3Yab" } | null,
///   "providers": [{ name, provider_type, base_url, default_model, is_default, enabled }, ...],
///   "agent": { max_iterations, max_retries, approval_timeout_minutes, concurrency: { per_conv } },
///   "app_settings": { "<allowed_key>": "<value>", "custom_prompt_present": bool, "custom_prompt_length"?: number }
/// }
/// ```
///
/// 全程只读:`list_all` / `load` / `get_all`,无任何 mutate 调用,无 IO 副作用。
pub(crate) async fn execute_get_app_config(ctx: &GetAppConfigCtx) -> anyhow::Result<Value> {
    let providers_repo = AiProviderRepo::new(&ctx.db);
    let settings_repo = SettingsRepo::new(&ctx.db);

    // ── providers:全部列表 + 默认项(带脱敏 key)──
    let providers = providers_repo
        .list_all()
        .await
        .map_err(|e| anyhow::anyhow!("读 ai_providers 失败: {}", e))?;

    // 默认 provider:取首条 is_default=true(list_all 已按 created_at DESC 排序,首条即最新)。
    // 不做 is_default 收敛写库(只读工具,收敛是 ai_list_providers IPC 的职责),脏数据照原样读。
    let default_idx = providers.iter().position(|p| p.is_default);

    // 解析默认 provider 的真实 key(keyring 异步,仅这一条调 keyring,避免列表全量解析拖慢)。
    let default_real_key = match default_idx {
        Some(i) => {
            let p = &providers[i];
            if !p.api_key.is_empty() {
                p.api_key.clone() // 未迁移(老明文)
            } else {
                get_provider_secret_async(p.id.clone())
                    .await
                    .unwrap_or_default() // 迁移后从 keyring
            }
        }
        None => String::new(),
    };

    let default_provider = match default_idx {
        Some(i) => json!(default_provider_view(&providers[i], &default_real_key)),
        None => Value::Null,
    };

    // 列表:全部 provider 概要(无 key 字段)。
    let providers_view: Vec<Value> = providers.iter().map(provider_summary).collect();

    // ── agent:内存原子量当前生效值(热改即反映,无需重启)──
    let max_iterations = ctx.agent_max_iterations.load(Ordering::SeqCst);
    let max_retries = ctx.agent_max_retries.load(Ordering::SeqCst);
    let approval_timeout_minutes = ctx.approval_timeout_minutes.load(Ordering::SeqCst);

    // concurrency:per_conv permits 可读(AtomicUsize);global 无 getter(Semaphore permits
    // 封装在 Arc<Mutex<Arc<Semaphore>>> 内层无读取接口),只返 per_conv 当前生效值。
    // global 默认 3,经 ai_set_concurrency_config 热改后内存生效但不暴露读取,故此处省略
    // 不返猜测值,避免误导 LLM(若 LLM 需 global 当前值,后续可在 LlmConcurrency 补 getter)。
    let per_conv_permits = ctx.llm_concurrency.current_per_conv_permits();

    let agent = json!({
        "max_iterations": max_iterations,
        "max_retries": max_retries,
        "approval_timeout_minutes": approval_timeout_minutes,
        "concurrency": {
            "per_conv": per_conv_permits,
        },
    });

    // ── app_settings:白名单筛选(防敏感 KV 泄露 + 防 prompt 噪音)──
    let all_settings = settings_repo
        .get_all()
        .await
        .map_err(|e| anyhow::anyhow!("读 app_settings 失败: {}", e))?;
    let mut app_settings = serde_json::Map::new();
    for (key, value) in all_settings {
        if ALLOWED_SETTING_KEYS.contains(&key.as_str()) {
            app_settings.insert(key, json!(value));
        }
    }
    // custom_prompt 仅返长度 + 是否存在,不返原文(防指令注入 + 防撑 prompt)。
    match settings_repo.get("custom_prompt").await {
        Ok(Some(cp)) => {
            let len = cp.chars().count();
            app_settings.insert("custom_prompt_present".into(), json!(true));
            app_settings.insert("custom_prompt_length".into(), json!(len));
        }
        _ => {
            app_settings.insert("custom_prompt_present".into(), json!(false));
        }
    }

    Ok(json!({
        "default_provider": default_provider,
        "providers": providers_view,
        "agent": agent,
        "app_settings": app_settings,
    }))
}

#[cfg(test)]
mod tests {
    use super::*;
    use df_storage::crud::AiProviderRepo;
    use df_storage::db::Database;
    use df_storage::models::AiProviderRecord;

    /// 构造测试用 GetAppConfigCtx(默认值,handler 实际执行读真实 db 数据)。
    fn make_ctx(db: Arc<Database>) -> GetAppConfigCtx {
        GetAppConfigCtx {
            db,
            agent_max_iterations: Arc::new(AtomicUsize::new(10)),
            agent_max_retries: Arc::new(AtomicUsize::new(3)),
            approval_timeout_minutes: Arc::new(AtomicU64::new(15)),
            llm_concurrency: LlmConcurrency::new(3, 2),
        }
    }

    /// 插入一条测试 provider(老明文路径:DB api_key 非空,不触发 keyring,测试无 OS 副作用)。
    async fn insert_provider(
        db: &Arc<Database>,
        id: &str,
        name: &str,
        api_key: &str,
        is_default: bool,
        enabled: bool,
    ) {
        let repo = AiProviderRepo::new(db);
        repo.insert(AiProviderRecord {
            id: id.to_string(),
            name: name.to_string(),
            provider_type: "openai_compat".to_string(),
            api_key: api_key.to_string(),
            base_url: "https://api.example.com".to_string(),
            default_model: "glm-4-flash".to_string(),
            models: None,
            model_configs: Vec::new(),
            is_default,
            config: None,
            created_at: "0".to_string(),
            updated_at: "0".to_string(),
            enabled,
            weight: 50,
        })
        .await
        .expect("insert provider");
    }

    /// 返回结构完整:四顶层键齐全 + providers 是数组。
    #[tokio::test]
    async fn returns_full_structure() {
        let db = Arc::new(Database::open_in_memory().await.unwrap());
        insert_provider(&db, "p1", "默认", "sk-abcdef123456", true, true).await;
        let ctx = make_ctx(db);
        let res = execute_get_app_config(&ctx).await.unwrap();

        // 四顶层键齐全
        assert!(res.get("default_provider").is_some(), "缺 default_provider");
        assert!(res.get("providers").is_some(), "缺 providers");
        assert!(res.get("agent").is_some(), "缺 agent");
        assert!(res.get("app_settings").is_some(), "缺 app_settings");
        // providers 是数组
        assert!(res["providers"].is_array(), "providers 应为数组");
        // agent 含四字段
        assert_eq!(res["agent"]["max_iterations"], 10);
        assert_eq!(res["agent"]["max_retries"], 3);
        assert_eq!(res["agent"]["approval_timeout_minutes"], 15);
        assert_eq!(res["agent"]["concurrency"]["per_conv"], 2);
    }

    /// api_key 脱敏:默认 provider 返前 4 + •••• + 后 4,非空 key 不返明文。
    #[tokio::test]
    async fn default_provider_api_key_masked() {
        let db = Arc::new(Database::open_in_memory().await.unwrap());
        // 13 字符 key:前 4 = sk-a, 后 4 = 3456, 中间 ••••
        insert_provider(&db, "p1", "默认", "sk-abcdef123456", true, true).await;
        let ctx = make_ctx(db);
        let res = execute_get_app_config(&ctx).await.unwrap();

        let masked = res["default_provider"]["api_key_masked"]
            .as_str()
            .expect("default_provider 应含 api_key_masked");
        assert_eq!(masked, "sk-a••••3456", "13 字符 key 应脱敏为 前4+••••+后4");
        // 绝不含完整明文
        assert!(!masked.contains("abcdef"), "脱敏值绝不含明文中段");
        assert!(!masked.contains("1234"), "脱敏值绝不含明文尾段(除最后 4)");
    }

    /// 列表项 providers 不含 api_key 字段(最小披露:仅默认项带脱敏 key)。
    #[tokio::test]
    async fn providers_list_has_no_api_key_field() {
        let db = Arc::new(Database::open_in_memory().await.unwrap());
        insert_provider(&db, "p1", "默认", "sk-abcdef123456", true, true).await;
        insert_provider(&db, "p2", "备用", "sk-xxxxxxxxxxxx", false, true).await;
        let ctx = make_ctx(db);
        let res = execute_get_app_config(&ctx).await.unwrap();

        let providers = res["providers"].as_array().unwrap();
        assert_eq!(providers.len(), 2, "应有 2 个 provider");
        for p in providers {
            assert!(
                p.get("api_key_masked").is_none(),
                "providers 列表项不应含 api_key_masked(仅默认项带)"
            );
            assert!(
                p.get("api_key").is_none(),
                "providers 列表项绝不含 api_key 字段"
            );
            // 列表项含必要概要字段
            assert!(p.get("name").is_some());
            assert!(p.get("provider_type").is_some());
            assert!(p.get("base_url").is_some());
            assert!(p.get("default_model").is_some());
            assert!(p.get("is_default").is_some());
            assert!(p.get("enabled").is_some());
        }
    }

    /// 空 key(未配置)→ api_key_masked 为空串,明确告知缺失而非误导性占位。
    #[tokio::test]
    async fn empty_api_key_returns_empty_string() {
        let db = Arc::new(Database::open_in_memory().await.unwrap());
        // DB api_key 空且 keyring 无此 id → resolve 返空 → handler 返空串
        // (keyring get_provider_secret_async 对不存在 id 返 None → unwrap_or_default 空串)
        insert_provider(&db, "p1", "无key", "", true, true).await;
        let ctx = make_ctx(db);
        let res = execute_get_app_config(&ctx).await.unwrap();

        let masked = res["default_provider"]["api_key_masked"]
            .as_str()
            .expect("default_provider 应含 api_key_masked");
        assert_eq!(masked, "", "空 key 应返空串(明确缺失),非误导性 mask 占位");
    }

    /// 无默认 provider → default_provider 为 null(不 panic,不臆造)。
    #[tokio::test]
    async fn no_default_provider_returns_null() {
        let db = Arc::new(Database::open_in_memory().await.unwrap());
        insert_provider(&db, "p1", "非默认", "sk-abcdef123456", false, true).await;
        let ctx = make_ctx(db);
        let res = execute_get_app_config(&ctx).await.unwrap();

        assert!(
            res["default_provider"].is_null(),
            "无默认 provider 时 default_provider 应为 null"
        );
        // providers 列表仍正常返
        assert_eq!(res["providers"].as_array().unwrap().len(), 1);
    }

    /// 短 key(≤8 字符)全脱敏为 •,不泄露长度外的信息(对齐 mask_api_key 规则)。
    #[tokio::test]
    async fn short_api_key_fully_masked() {
        let db = Arc::new(Database::open_in_memory().await.unwrap());
        insert_provider(&db, "p1", "短key", "sk-ab", true, true).await;
        let ctx = make_ctx(db);
        let res = execute_get_app_config(&ctx).await.unwrap();

        let masked = res["default_provider"]["api_key_masked"]
            .as_str()
            .unwrap();
        assert_eq!(masked, "•••••", "5 字符 key 应全脱敏为 5 个 •(≤8 全脱敏)");
    }
}
