//! 知识生命线记录器 — 统一入口,各业务流程只调一行
//!
//! 设计目标:
//! - 事件记录逻辑不散落在各业务流程内部(提取/注入/状态变更),集中于此模块
//! - fire-and-forget 友好:便捷方法内部吞掉错误(只 warn),调用方无需处理 Result
//! - 易扩展:新事件类型只加一个便捷方法 + 一行 context 构造,不改主流程

use std::sync::Arc;

use df_types::types::new_id;
use df_storage::crud::KnowledgeEventsRepo;
use df_storage::db::Database;
use df_storage::models::KnowledgeEventRecord;

use super::{err_str, now_millis};

/// 事件类型常量(生命线节点;归档复用 status_changed 的 to=archived,不单列)
pub const EVENT_CREATED: &str = "created";
pub const EVENT_EXTRACTED: &str = "extracted";
pub const EVENT_STATUS_CHANGED: &str = "status_changed";
pub const EVENT_REFERENCED: &str = "referenced";
/// 编辑产生(人工/前端编辑 title/content/tags/confidence/reasoning)
/// P1 修复(KP-5 审计断档):knowledge_update 编辑路径此前无生命线事件,
/// 与 knowledge_create(created)/knowledge_update_status(status_changed) 不对称,
/// 详情页生命线在编辑后会断档。新增 updated 事件补齐。
pub const EVENT_UPDATED: &str = "updated";

/// 知识生命线记录器 — 持有 DB 句柄,提供便捷事件写入方法
///
/// 用法:`KnowledgeTimeline::new(&state.db).record_xxx(...).await`
///
/// 异步策略(按路径热度,非 bug,刻意不一致):
/// - 热路径(对话注入 build_knowledge_context / 提炼 extract):整块已在 spawn 任务内,
///   此处直接 await = 对用户 fire-and-forget,不阻塞 AI 对话
/// - 低频命令(状态变更 / 创建):事件写入 ms 级,直接 await 比 detached spawn 更简单
/// 便捷方法内部 `fire()` 已吞错误(warn 不阻断),调用方无需处理 Result。
pub struct KnowledgeTimeline {
    db: Arc<Database>,
}

impl KnowledgeTimeline {
    pub fn new(db: &Arc<Database>) -> Self {
        Self { db: db.clone() }
    }

    /// 通用写入(底层入口,便捷方法均经此)
    pub async fn record(
        &self,
        knowledge_id: &str,
        event_type: &str,
        source_ref: Option<String>,
        context_json: Option<String>,
    ) -> Result<(), String> {
        let record = KnowledgeEventRecord {
            id: new_id(),
            knowledge_id: knowledge_id.to_string(),
            event_type: event_type.to_string(),
            source_ref,
            context_json,
            timestamp: now_millis(),
        };
        KnowledgeEventsRepo::new(&self.db)
            .insert(record)
            .await
            .map_err(err_str)?;
        Ok(())
    }

    /// 手动录入产生(context: method)
    pub async fn record_created(&self, knowledge_id: &str, method: &str) {
        let ctx = serde_json::json!({ "method": method }).to_string();
        self.fire(knowledge_id, EVENT_CREATED, Some("manual".into()), Some(ctx)).await;
    }

    /// 编辑产生(context: fields 改动字段列表 / source_ref 编辑入口溯源)
    ///
    /// P1 修复(KP-5 审计断档):由 knowledge_update(编辑 title/content/tags/confidence/reasoning)
    /// 调用,补齐编辑路径缺失的生命线事件(此前仅 create/update_status 有事件,编辑后生命线断档)。
    /// - `fields`: 本次实际改动的字段名列表(如 ["title","tags"]),用于审计/展示改了什么
    /// - `source_ref`: 编辑入口溯源,如 "ui:detail" / "ui:inbox",前端调用方传入
    pub async fn record_updated(&self, knowledge_id: &str, fields: &[&str], source_ref: &str) {
        let ctx = serde_json::json!({ "fields": fields }).to_string();
        self.fire(
            knowledge_id,
            EVENT_UPDATED,
            Some(source_ref.to_string()),
            Some(ctx),
        )
        .await;
    }

    /// AI 对话提炼产生(context: conv_id / conv_title / reasoning)
    ///
    /// F-260619-04 P1 消息级溯源:`message_id` 为末条 assistant 消息 id(本轮 AI 产出知识的载体)。
    /// - Some(id) → source_ref = `conv_msg:{id}`(消息级)
    /// - None(老数据无 id) → source_ref = `conv:{conv_id}`(对话级,向前兼容)
    pub async fn record_extracted(
        &self,
        knowledge_id: &str,
        conv_id: &str,
        conv_title: &str,
        reasoning: &str,
        message_id: Option<&str>,
    ) {
        let ctx = serde_json::json!({
            "conv_id": conv_id,
            "conv_title": conv_title,
            "reasoning": reasoning,
        })
        .to_string();
        let source_ref = match message_id {
            Some(mid) => format!("conv_msg:{}", mid),
            None => format!("conv:{}", conv_id),
        };
        self.fire(
            knowledge_id,
            EVENT_EXTRACTED,
            Some(source_ref),
            Some(ctx),
        )
        .await;
    }

    /// 被检索命中/注入(context: conv_id / query)
    ///
    /// F-260619-04 P1 消息级溯源:`message_id` 为触发检索的 user 消息 id(用户问题命中知识)。
    /// - Some(id) → source_ref = `conv_msg:{id}`(消息级)
    /// - None(老数据无 id) → source_ref = `conv:{conv_id}`(对话级,向前兼容)
    pub async fn record_referenced(
        &self,
        knowledge_id: &str,
        conv_id: &str,
        query: &str,
        message_id: Option<&str>,
    ) {
        let ctx = serde_json::json!({ "conv_id": conv_id, "query": query }).to_string();
        let source_ref = match message_id {
            Some(mid) => format!("conv_msg:{}", mid),
            None => format!("conv:{}", conv_id),
        };
        self.fire(
            knowledge_id,
            EVENT_REFERENCED,
            Some(source_ref),
            Some(ctx),
        )
        .await;
    }

    /// 状态变更(context: from / to;to=published=审核通过,to=archived=归档)
    pub async fn record_status_change(&self, knowledge_id: &str, from: &str, to: &str) {
        let ctx = serde_json::json!({ "from": from, "to": to }).to_string();
        self.fire(knowledge_id, EVENT_STATUS_CHANGED, None, Some(ctx)).await;
    }

    /// 内部:写入并吞错误(fire-and-forget,失败只 warn 不阻断主流程)
    async fn fire(
        &self,
        knowledge_id: &str,
        event_type: &str,
        source_ref: Option<String>,
        context_json: Option<String>,
    ) {
        if let Err(e) = self.record(knowledge_id, event_type, source_ref, context_json).await {
            tracing::warn!("知识生命线事件写入失败(非阻断) [{}]: {}", event_type, e);
        }
    }
}
