//! 知识库相关命令 — 共享记忆层(沉淀 / 检索注入 / 审核收件箱 / 配置)
//!
//! 11 个 command 对齐 MCP 语义(search/list/get/create/update_status/record_reuse 可对外暴露,
//! archive/get_config/save_config/extract_now/list_candidates 为内部便利方法)。

use serde::Deserialize;
use tauri::State;

use df_types::types::new_id;
use df_storage::models::{KnowledgeEventRecord, KnowledgeRecord};

use crate::state::{AppState, KnowledgeConfig};

use super::knowledge_timeline::KnowledgeTimeline;
use super::{err_str, now_millis};
use serde::Serialize;

/// 创建知识入参
#[derive(Debug, Deserialize)]
pub struct CreateKnowledgeInput {
    /// 7 种 KnowledgeKind snake_case 之一
    pub kind: String,
    pub title: String,
    pub content: String,
    /// 标签 JSON 数组字符串
    #[serde(default)]
    pub tags: Option<String>,
    #[serde(default)]
    pub source_project: Option<String>,
    #[serde(default)]
    pub source_ref: Option<String>,
    /// high | medium | low
    #[serde(default)]
    pub confidence: Option<String>,
}

/// 检索入参
#[derive(Debug, Deserialize)]
pub struct KnowledgeSearchInput {
    pub query: String,
    #[serde(default)]
    pub kind: Option<String>,
    #[serde(default)]
    pub limit: Option<usize>,
}

/// 状态转换合法矩阵校验
///
/// candidate → pending_review | published | archived
/// pending_review → published | archived
/// published → archived
/// (其他组合非法)
fn validate_transition(from: &str, to: &str) -> Result<(), String> {
    let legal = match from {
        "candidate" => matches!(to, "pending_review" | "published" | "archived"),
        "pending_review" => matches!(to, "published" | "archived"),
        "published" => matches!(to, "archived"),
        _ => false,
    };
    if legal {
        Ok(())
    } else {
        Err(format!("非法状态转换: {from} → {to}"))
    }
}

// ============================================================
// CRUD
// ============================================================

/// 列出知识 — 可按 status 筛选,status=None 时默认仅返回 published
///
/// 默认范围说明(F-260616-02 决策 a):
///   - status=None → library tab 的数据源,语义为「已发布知识库」,仅 published。
///     不再混杂 pending_review(pending_review 归 inbox 收件箱,见 knowledge_list_candidates)。
///   - 显式传 status 时按该 status 过滤(含 archived)。
#[tauri::command]
pub async fn knowledge_list(
    state: State<'_, AppState>,
    status: Option<String>,
) -> Result<Vec<KnowledgeRecord>, String> {
    match status {
        // 显式传 status 时按该 status 过滤(含 archived)
        Some(s) => state.knowledge.list_by_status(&s).await.map_err(err_str),
        // 默认:library 仅 published(F-260616-02 决策 a,职责清晰:library=published,inbox=待处理)
        None => state.knowledge.list_by_status("published").await.map_err(err_str),
    }
}

/// 单条查询
#[tauri::command]
pub async fn knowledge_get(
    state: State<'_, AppState>,
    id: String,
) -> Result<KnowledgeRecord, String> {
    state
        .knowledge
        .get_by_id(&id)
        .await
        .map_err(err_str)?
        .ok_or_else(|| format!("知识不存在: {id}"))
}

/// 检索知识(top-N,默认 3) — MCP search tool
#[tauri::command]
pub async fn knowledge_search(
    state: State<'_, AppState>,
    input: KnowledgeSearchInput,
) -> Result<Vec<KnowledgeRecord>, String> {
    let limit = input.limit.unwrap_or(3).min(20);
    state
        .knowledge
        .search(&input.query, input.kind.as_deref(), limit)
        .await
        .map_err(err_str)
}

/// 创建知识(始终 candidate 状态) — MCP create tool
#[tauri::command]
pub async fn knowledge_create(
    state: State<'_, AppState>,
    input: CreateKnowledgeInput,
) -> Result<KnowledgeRecord, String> {
    let now = now_millis();
    let record = KnowledgeRecord {
        id: new_id(),
        kind: input.kind,
        title: input.title,
        content: input.content,
        tags: input.tags,
        status: "candidate".to_string(),
        confidence: input.confidence,
        reuse_count: 0,
        verified: false,
        source_project: input.source_project,
        source_ref: input.source_ref,
        reasoning: None,
        embedding_status: None,
        created_at: now.clone(),
        updated_at: now,
    };
    state
        .knowledge
        .insert(record.clone())
        .await
        .map_err(err_str)?;
    // 生命线:手动录入产生(fire-and-forget)
    KnowledgeTimeline::new(&state.db)
        .record_created(&record.id, "manual")
        .await;
    Ok(record)
}

// ============================================================
// 状态机 + 计数
// ============================================================

/// 状态转换(含合法矩阵校验) — MCP update_status tool
#[tauri::command]
pub async fn knowledge_update_status(
    state: State<'_, AppState>,
    id: String,
    status: String,
) -> Result<bool, String> {
    let record = state
        .knowledge
        .get_by_id(&id)
        .await
        .map_err(err_str)?
        .ok_or_else(|| format!("知识不存在: {id}"))?;

    let old_status = record.status.clone();
    validate_transition(&record.status, &status)?;

    // published 时一次性标 verified=true(发布审核标)
    let now = now_millis();
    let updated = KnowledgeRecord {
        status: status.clone(),
        verified: if status == "published" { true } else { record.verified },
        updated_at: now,
        ..record
    };
    let result = state
        .knowledge
        .update_full(&updated)
        .await
        .map_err(err_str)?;

    // 生命线:状态变更(fire-and-forget;to=published=审核通过,to=archived=归档)
    KnowledgeTimeline::new(&state.db)
        .record_status_change(&id, &old_status, &status)
        .await;

    // 发布时后台生成嵌入(只有 published 参与检索,candidate 不浪费 embed 调用;
    // vector_enabled 关闭或 embed 失败时该条走 LIKE 降级,非阻断)
    if status == "published" {
        crate::commands::ai::spawn_embedding_for_knowledge(&state, &updated).await;
    }

    Ok(result)
}

/// 复用计数 +1(检索命中时调用) — MCP record_reuse tool
#[tauri::command]
pub async fn knowledge_record_reuse(
    state: State<'_, AppState>,
    id: String,
) -> Result<bool, String> {
    state
        .knowledge
        .increment_reuse_count(&id)
        .await
        .map_err(err_str)
}

/// 收件箱 — 列出待处理条目(candidate + pending_review),按 confidence 语义排序
///
/// 语义(F-260616-02 决策 a):inbox = 「待处理」收件箱,聚合 candidate(待评估)
/// 与 pending_review(待发布审核)两种待处理状态。library 仅 published。
///
/// 实现:list_by_status 单状态查询,这里合并 candidate 与 pending_review 两路结果。
/// 两路各自已按 `confidence DESC, created_at DESC` 排序,有序合并保持同一规则。
#[tauri::command]
pub async fn knowledge_list_candidates(
    state: State<'_, AppState>,
) -> Result<Vec<KnowledgeRecord>, String> {
    let (candidates, pending) = tokio::try_join!(
        state.knowledge.list_by_status("candidate"),
        state.knowledge.list_by_status("pending_review"),
    )
    .map_err(err_str)?;
    Ok(merge_by_confidence(candidates, pending))
}

/// 有序合并两列(各自已按 confidence DESC, created_at DESC 排序),结果保持同序。
///
/// confidence 排序权重:high=3, medium=2, low=1, 其他=0;同权重按 created_at DESC
/// (字符串毫秒时间戳字典序 = 时间序)。等价 SQL `ORDER BY CASE confidence ... DESC, created_at DESC`。
fn merge_by_confidence(
    mut a: Vec<KnowledgeRecord>,
    mut b: Vec<KnowledgeRecord>,
) -> Vec<KnowledgeRecord> {
    use std::cmp::Ordering;
    fn rank(c: &str) -> i8 {
        match c {
            "high" => 3,
            "medium" => 2,
            "low" => 1,
            _ => 0,
        }
    }
    let cmp = |x: &KnowledgeRecord, y: &KnowledgeRecord| -> Ordering {
        let rx = rank(x.confidence.as_deref().unwrap_or(""));
        let ry = rank(y.confidence.as_deref().unwrap_or(""));
        ry.cmp(&rx) // confidence DESC
            .then_with(|| y.created_at.cmp(&x.created_at)) // created_at DESC
    };
    a.sort_by(cmp);
    b.sort_by(cmp);
    let mut out = Vec::with_capacity(a.len() + b.len());
    let (mut i, mut j) = (0, 0);
    while i < a.len() && j < b.len() {
        if cmp(&a[i], &b[j]) != Ordering::Greater {
            out.push(a[i].clone());
            i += 1;
        } else {
            out.push(b[j].clone());
            j += 1;
        }
    }
    out.extend_from_slice(&a[i..]);
    out.extend_from_slice(&b[j..]);
    out
}

/// 归档(软删除) — UPDATE status='archived'
#[tauri::command]
pub async fn knowledge_archive(
    state: State<'_, AppState>,
    id: String,
) -> Result<bool, String> {
    knowledge_update_status(state, id, "archived".to_string()).await
}

/// 重新生成向量嵌入(补偿重试)— P1 修复(嵌入失败无标记)的补偿入口
///
/// 触发条件:embedding_status='failed' 的已发布知识(provider 临时不可用导致永久无向量索引)。
/// 行为:fire-and-forget,后台逐条重跑嵌入生成(成功置 done,失败仍 failed 可再次触发)。
/// 返回值:本次触发重试的条数(0 表示无 failed 条目或 vector_enabled 关闭)。
#[tauri::command]
pub async fn knowledge_retry_embedding(
    state: State<'_, AppState>,
) -> Result<usize, String> {
    let count = crate::commands::ai::retry_failed_embeddings(&state)
        .await
        .map_err(err_str)?;
    Ok(count)
}

// ============================================================
// 配置 + 手动提炼
// ============================================================

/// 读取知识库行为配置(提取 + 注入)
#[tauri::command]
pub async fn knowledge_get_config(
    state: State<'_, AppState>,
) -> Result<KnowledgeConfig, String> {
    let cfg = state.knowledge_config.lock().await;
    Ok(cfg.clone())
}

/// 保存知识库行为配置
#[tauri::command]
pub async fn knowledge_save_config(
    state: State<'_, AppState>,
    config: KnowledgeConfig,
) -> Result<bool, String> {
    // P0(设置走查-2026-06-21):落 Settings KV 持久化,reload_knowledge_config 启动恢复
    // (原纯内存 Arc<Mutex> 启动 default 覆盖致 8 项配置重启全丢)。
    let json = serde_json::to_string(&config).map_err(|e| e.to_string())?;
    {
        let mut cfg = state.knowledge_config.lock().await;
        *cfg = config;
    }
    state
        .settings
        .set(crate::state::KNOWLEDGE_CONFIG_KEY, &json)
        .await
        .map_err(|e| e.to_string())?;
    Ok(true)
}

/// 手动触发提炼(ManualOnly 模式 / 用户主动点按钮)
///
/// 实际提炼逻辑在 commands::ai 模块(需访问 active provider + conversation messages)。
/// 此 command 仅作前端入口,委托给 ai 模块的提炼函数。
#[tauri::command]
pub async fn knowledge_extract_now(
    state: State<'_, AppState>,
) -> Result<bool, String> {
    crate::commands::ai::trigger_extraction_now(&state).await
}

// ============================================================
// 生命线:详情 / 编辑 / 事件查询
// ============================================================

/// 编辑知识入参(部分更新,仅传需改字段)
#[derive(Debug, Deserialize)]
pub struct UpdateKnowledgeInput {
    #[serde(default)]
    pub title: Option<String>,
    #[serde(default)]
    pub content: Option<String>,
    #[serde(default)]
    pub tags: Option<String>,
    #[serde(default)]
    pub confidence: Option<String>,
    #[serde(default)]
    pub reasoning: Option<String>,
}

/// 知识详情聚合负载(基本信息 + 全部生命线事件)
#[derive(Debug, Serialize)]
pub struct KnowledgeDetailPayload {
    pub knowledge: KnowledgeRecord,
    pub events: Vec<KnowledgeEventRecord>,
}

/// 知识详情(基本信息 + 生命线事件) — 详情页一次拉全
#[tauri::command]
pub async fn knowledge_get_detail(
    state: State<'_, AppState>,
    id: String,
) -> Result<KnowledgeDetailPayload, String> {
    let knowledge = state
        .knowledge
        .get_by_id(&id)
        .await
        .map_err(err_str)?
        .ok_or_else(|| format!("知识不存在: {id}"))?;
    let events = state
        .knowledge_events
        .list_by_knowledge(&id)
        .await
        .map_err(err_str)?;
    Ok(KnowledgeDetailPayload { knowledge, events })
}

/// 编辑知识(部分更新 title/content/tags/confidence/reasoning)
#[tauri::command]
pub async fn knowledge_update(
    state: State<'_, AppState>,
    id: String,
    input: UpdateKnowledgeInput,
) -> Result<KnowledgeRecord, String> {
    let mut record = state
        .knowledge
        .get_by_id(&id)
        .await
        .map_err(err_str)?
        .ok_or_else(|| format!("知识不存在: {id}"))?;
    // P1 修复(KP-5 审计断档):记录本次实际改动的字段,供 updated 生命线事件审计展示。
    // 只在值确实变化时计入 changed_fields(避免无改动也产生 updated 噪音事件)。
    let mut changed_fields: Vec<&'static str> = Vec::new();
    if let Some(v) = input.title {
        if record.title != v {
            changed_fields.push("title");
        }
        record.title = v;
    }
    if let Some(v) = input.content {
        if record.content != v {
            changed_fields.push("content");
        }
        record.content = v;
    }
    if let Some(v) = input.tags {
        if record.tags.as_deref() != Some(v.as_str()) {
            changed_fields.push("tags");
        }
        record.tags = Some(v);
    }
    // 空串 = 清空(前端编辑器清空 confidence/reasoning 输入框时传 "")
    if let Some(v) = input.confidence {
        let new_conf = if v.is_empty() { None } else { Some(v) };
        if record.confidence != new_conf {
            changed_fields.push("confidence");
        }
        record.confidence = new_conf;
    }
    if let Some(v) = input.reasoning {
        let new_reason = if v.is_empty() { None } else { Some(v) };
        if record.reasoning != new_reason {
            changed_fields.push("reasoning");
        }
        record.reasoning = new_reason;
    }
    record.updated_at = now_millis();
    state
        .knowledge
        .update_full(&record)
        .await
        .map_err(err_str)?;
    // 生命线:编辑产生(fire-and-forget)。
    // 仅在确有字段改动时记录,无改动不产噪音事件(对齐 create/update_status 的语义粒度)。
    // source_ref="ui" 标识编辑来自前端 UI(未来可细分 detail/inbox,当前统一 ui)。
    if !changed_fields.is_empty() {
        KnowledgeTimeline::new(&state.db)
            .record_updated(&id, &changed_fields, "ui")
            .await;
    }
    Ok(record)
}

/// 查询生命线事件(可按 event_type 过滤 + limit)
#[tauri::command]
pub async fn knowledge_events(
    state: State<'_, AppState>,
    knowledge_id: String,
    event_type: Option<String>,
    limit: Option<usize>,
) -> Result<Vec<KnowledgeEventRecord>, String> {
    match event_type {
        Some(et) => {
            let limit = limit.unwrap_or(50);
            state
                .knowledge_events
                .list_by_knowledge_type(&knowledge_id, &et, limit)
                .await
                .map_err(err_str)
        }
        None => state
            .knowledge_events
            .list_by_knowledge(&knowledge_id)
            .await
            .map_err(err_str),
    }
}

#[cfg(test)]
mod tests {
    use super::validate_transition;

    #[test]
    fn candidate_legal_transitions() {
        assert!(validate_transition("candidate", "pending_review").is_ok());
        assert!(validate_transition("candidate", "published").is_ok());
        assert!(validate_transition("candidate", "archived").is_ok());
    }

    #[test]
    fn candidate_illegal_transitions() {
        // 禁止自转、回退
        assert!(validate_transition("candidate", "candidate").is_err());
    }

    #[test]
    fn pending_review_legal_transitions() {
        assert!(validate_transition("pending_review", "published").is_ok());
        assert!(validate_transition("pending_review", "archived").is_ok());
    }

    #[test]
    fn pending_review_illegal_transitions() {
        // 已进入审核态不能再退回 candidate
        assert!(validate_transition("pending_review", "candidate").is_err());
        assert!(validate_transition("pending_review", "pending_review").is_err());
    }

    #[test]
    fn published_legal_transitions() {
        // 已发布只能归档
        assert!(validate_transition("published", "archived").is_ok());
    }

    #[test]
    fn published_illegal_transitions() {
        // 发布态不可再进审核、不可回 candidate、不可自转、不可重复 published
        assert!(validate_transition("published", "pending_review").is_err());
        assert!(validate_transition("published", "candidate").is_err());
        assert!(validate_transition("published", "published").is_err());
    }

    #[test]
    fn archived_is_terminal() {
        // 归档为终态,任何转换都非法
        for to in ["candidate", "pending_review", "published", "archived"] {
            assert!(validate_transition("archived", to).is_err(),
                "archived → {to} 应为非法");
        }
    }

    #[test]
    fn unknown_from_is_illegal() {
        // 未知源状态一律拒绝
        assert!(validate_transition("unknown", "published").is_err());
        assert!(validate_transition("", "archived").is_err());
    }

    #[test]
    fn illegal_target_is_rejected() {
        // 合法源 → 未知/空目标一律拒绝
        assert!(validate_transition("candidate", "unknown").is_err());
        assert!(validate_transition("candidate", "").is_err());
        assert!(validate_transition("pending_review", "draft").is_err());
    }

    #[test]
    fn error_message_contains_transition() {
        let err = validate_transition("archived", "candidate").unwrap_err();
        assert!(err.contains("archived"), "错误信息应包含源状态: {err}");
        assert!(err.contains("candidate"), "错误信息应包含目标状态: {err}");
    }
}
