//! 灵感评估历史 Repo:IdeaEvalRepo(追加型审计表 idea_evaluations,V22)
//!
//! 每次灵感 AI 评估追加一行快照(version 单调递增),保留评估历史可追溯。
//! 对齐 [`KnowledgeEventsRepo`](crate::crud::KnowledgeEventsRepo) 模式:
//! `impl_repo!` 宏生成基础 CRUD(insert/get_by_id/list_all/query/update_field/delete/
//! update_full)+ 额外专用方法 `list_by_idea`(按 idea_id 过滤,version DESC 排序)。
//!
//! # ⚠️ 宏 list_all / query 不可用(表无 created_at)
//!
//! 本表时间列是 `evaluated_at`(非 `created_at`),但 `impl_repo!` 宏生成的
//! `list_all` / `query` 硬编码 `ORDER BY created_at DESC`(见 crud/mod.rs 宏内
//! `ORDER BY created_at DESC` 字面量)。误调 `state.idea_eval.list_all()` 或
//! `state.idea_eval.query(...)` 会触发 SQLite "no such column: created_at",运行时崩。
//! **跨灵感按时间倒序浏览评估历史须走专用方法 [`IdeaEvalRepo::list_recent_idea_evals`]**
//! (按 evaluated_at DESC + limit 钳制 200),对标 `KnowledgeEventsRepo::list_recent`
//! 对 knowledge_events 表的同款兜底处理(那张表亦无 created_at,时间列名是 timestamp)。

use std::sync::Arc;

use rusqlite::{params, Connection, OptionalExtension, Row};
use tokio::sync::Mutex;

use df_types::error::Result;

use crate::db::Database;
use crate::models::IdeaEvaluationRecord;

use super::impl_repo;
use super::{now_millis_str, storage_err, validate_column_name};

// ============================================================
// from_row 辅助函数
// ============================================================

/// 按 name 取 idea_evaluations 表 8 列 → IdeaEvaluationRecord。
fn idea_eval_from_row(row: &Row<'_>) -> std::result::Result<IdeaEvaluationRecord, rusqlite::Error> {
    Ok(IdeaEvaluationRecord {
        id: row.get("id")?,
        idea_id: row.get("idea_id")?,
        version: row.get("version")?,
        ai_analysis: row.get("ai_analysis")?,
        scores: row.get("scores")?,
        score: row.get("score")?,
        evaluated_by: row.get("evaluated_by")?,
        evaluated_at: row.get("evaluated_at")?,
    })
}

// ============================================================
// Repo 实现
// ============================================================

impl_repo!(
    /// 灵感评估历史表 CRUD(追加型审计表,只增不改)
    IdeaEvalRepo,
    IdeaEvaluationRecord,
    "idea_evaluations",
    from_row => |row| idea_eval_from_row(row),
    insert  => |conn, rec| {
        conn.execute(
            "INSERT INTO idea_evaluations (id, idea_id, version, ai_analysis, scores, score, evaluated_by, evaluated_at)
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
            params![
                rec.id, rec.idea_id, rec.version, rec.ai_analysis,
                rec.scores, rec.score, rec.evaluated_by, rec.evaluated_at
            ],
        )
    },
    update  => |_conn, _rec| {
        // 加固(审计不可篡改):追加型审计表语义禁止 UPDATE(历史快照只增不改,版本单调递增)。
        // 宏(impl_repo!)要求 update 闭包生成 update_full,但本表设计上仅 insert 追加新版本。
        // 若有调用方误调 update_full → 返回 Err(SQLITE_ERROR)而非真执行 UPDATE,杜绝历史被
        // 静默篡改(否则审计追溯失真,版本单调性被破坏)。Err 经 storage_err 映射为
        // Error::Storage,调用方拿到 Err 可发现误用。注:update_field 走宏通用 SQL 路径,
        // 不经此闭包,故本表 update_field 同样不应被调用(调用方约束,非编译期保证)。
        //
        // 显式标注 rusqlite::Result<usize>(宏吃闭包 expr 不能写 -> 返回类型,改 let 绑定标注):
        // 闭包返回 Err 经 update_full 的 .map_err(storage_err)? → Err(Error::Storage),
        // affected=usize 的类型锚点确保宏内 `affected > 0` 编译。
        let res: rusqlite::Result<usize> = Err(rusqlite::Error::SqliteFailure(
            rusqlite::ffi::Error::new(rusqlite::ffi::SQLITE_ERROR),
            Some("idea_evaluations 为追加型审计表,禁止 UPDATE(仅 insert 追加新版本)".to_string()),
        ));
        res
    }
);

impl IdeaEvalRepo {
    /// 按 idea_id 查询全部评估历史(version DESC,最新版本在前)。
    ///
    /// 对标 [`KnowledgeEventsRepo::list_by_knowledge`](crate::crud::KnowledgeEventsRepo::list_by_knowledge)
    /// 的 spawn_blocking + prepare + query_map 模式。前端取最新评估直接取返回 Vec 首元素。
    pub async fn list_by_idea(&self, idea_id: &str) -> Result<Vec<IdeaEvaluationRecord>> {
        let conn = self.conn.clone();
        let idea_id = idea_id.to_owned();
        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();
            let mut stmt = guard
                .prepare(
                    "SELECT id,idea_id,version,ai_analysis,scores,score,evaluated_by,evaluated_at \
                     FROM idea_evaluations WHERE idea_id = ?1 ORDER BY version DESC",
                )
                .map_err(storage_err)?;
            let rows = stmt
                .query_map(params![idea_id], |row| idea_eval_from_row(row))
                .map_err(storage_err)?;
            let mut results = Vec::new();
            for r in rows {
                results.push(r.map_err(storage_err)?);
            }
            Ok(results)
        })
        .await
        .map_err(storage_err)?
    }

    /// 跨灵感列最近 N 条评估历史(全表 evaluated_at DESC,top-N)。
    ///
    /// **专用兜底方法**:本表时间列名是 `evaluated_at` 而非 `created_at`,但
    /// `impl_repo!` 宏生成的 `query()` / `list_all()` 硬编码 `ORDER BY created_at`
    /// (见 crud/mod.rs 宏内 `ORDER BY created_at DESC` 字面量),误调
    /// `state.idea_eval.list_all()` 或 `state.idea_eval.query(...)` 会触发 SQLite
    /// "no such column: created_at"。本方法走专用 SELECT 绕过宏硬编码,供需要跨灵感
    /// 按评估时间倒序浏览历史的调用方使用(对标 [`KnowledgeEventsRepo::list_recent`]
    /// 对 knowledge_events 表的同款兜底处理——那张表同样无 created_at,时间列名是 timestamp)。
    /// limit 上限钳制 200,防前端恶意/失误传超大值。
    pub async fn list_recent_idea_evals(&self, limit: u32) -> Result<Vec<IdeaEvaluationRecord>> {
        let conn = self.conn.clone();
        // 钳制 limit 防滥用(最大 200)
        let safe_limit = limit.min(200) as i64;
        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();
            let mut stmt = guard
                .prepare(
                    "SELECT id,idea_id,version,ai_analysis,scores,score,evaluated_by,evaluated_at \
                     FROM idea_evaluations ORDER BY evaluated_at DESC LIMIT ?1",
                )
                .map_err(storage_err)?;
            let rows = stmt
                .query_map(params![safe_limit], |row| idea_eval_from_row(row))
                .map_err(storage_err)?;
            let mut results = Vec::new();
            for r in rows {
                results.push(r.map_err(storage_err)?);
            }
            Ok(results)
        })
        .await
        .map_err(storage_err)?
    }
}

// ============================================================
// 单元测试 — IdeaEvalRepo 内存 DB(insert + list_by_idea 排序)
// ============================================================

#[cfg(test)]
mod tests {
    use super::*;
    use crate::db::Database;

    fn erec(id: &str, idea_id: &str, version: i64, score: Option<f64>) -> IdeaEvaluationRecord {
        IdeaEvaluationRecord {
            id: id.to_string(),
            idea_id: idea_id.to_string(),
            version,
            ai_analysis: Some("{\"ok\":true}".to_string()),
            scores: Some("{\"overall\":1}".to_string()),
            score,
            evaluated_by: Some("glm-4".to_string()),
            evaluated_at: "1700000000000".to_string(),
        }
    }

    async fn setup_repo() -> IdeaEvalRepo {
        let db = Database::open_in_memory().await.expect("open_in_memory");
        IdeaEvalRepo::new(&db)
    }

    #[tokio::test]
    async fn list_by_idea_orders_version_desc() {
        let repo = setup_repo().await;
        repo.insert(erec("e1", "idea_a", 1, Some(0.5))).await.unwrap();
        repo.insert(erec("e2", "idea_a", 3, Some(0.9))).await.unwrap();
        repo.insert(erec("e3", "idea_a", 2, Some(0.7))).await.unwrap();
        repo.insert(erec("e4", "idea_b", 1, Some(0.1))).await.unwrap();

        let list = repo.list_by_idea("idea_a").await.unwrap();
        assert_eq!(list.len(), 3, "仅 idea_a 的 3 条评估");
        let versions: Vec<i64> = list.iter().map(|r| r.version).collect();
        assert_eq!(versions, vec![3, 2, 1], "version DESC: 最新在前");

        let list_b = repo.list_by_idea("idea_b").await.unwrap();
        assert_eq!(list_b.len(), 1);
        assert_eq!(list_b[0].version, 1);
    }

    #[tokio::test]
    async fn list_by_idea_empty_for_unknown_idea() {
        let repo = setup_repo().await;
        let list = repo.list_by_idea("nope").await.unwrap();
        assert!(list.is_empty());
    }

    #[tokio::test]
    async fn list_recent_idea_evals_orders_evaluated_at_desc_and_clamps_limit() {
        // 兜底方法:跨灵感按 evaluated_at DESC + limit 钳制 200(规避宏硬编码
        // ORDER BY created_at 致本表崩)。evaluated_at 为毫秒字符串,字典序与时间序一致。
        let repo = setup_repo().await;
        // erec 默认 evaluated_at 同值,这里覆盖以验证排序
        let mut a = erec("e1", "idea_a", 1, Some(0.5));
        a.evaluated_at = "1700000000001".to_string();
        let mut b = erec("e2", "idea_b", 1, Some(0.6));
        b.evaluated_at = "1700000000003".to_string();
        let mut c = erec("e3", "idea_a", 2, Some(0.7));
        c.evaluated_at = "1700000000002".to_string();
        repo.insert(a).await.unwrap();
        repo.insert(b).await.unwrap();
        repo.insert(c).await.unwrap();

        // limit=10 取全部,按 evaluated_at DESC(跨灵感)
        let list = repo.list_recent_idea_evals(10).await.unwrap();
        assert_eq!(list.len(), 3);
        let times: Vec<&str> = list.iter().map(|r| r.evaluated_at.as_str()).collect();
        assert_eq!(times, vec!["1700000000003", "1700000000002", "1700000000001"]);

        // limit=2 截断到前 2
        let list2 = repo.list_recent_idea_evals(2).await.unwrap();
        assert_eq!(list2.len(), 2);
        assert_eq!(list2[0].id, "e2");
        assert_eq!(list2[1].id, "e3");

        // limit 钳制:超大值被压到 200(不会崩,仅返回实际行数)
        let list_big = repo.list_recent_idea_evals(u32::MAX).await.unwrap();
        assert_eq!(list_big.len(), 3, "u32::MAX 钳制到 200,但表仅 3 行");

        // 空表
        let repo_empty = setup_repo().await;
        assert!(repo_empty.list_recent_idea_evals(10).await.unwrap().is_empty());
    }

    #[tokio::test]
    async fn insert_then_get_by_id_roundtrip() {
        let repo = setup_repo().await;
        let rec = erec("e1", "idea_a", 1, Some(0.42));
        let id = repo.insert(rec.clone()).await.unwrap();
        assert_eq!(id, "e1");
        let got = repo.get_by_id("e1").await.unwrap().expect("记录存在");
        assert_eq!(got.idea_id, "idea_a");
        assert_eq!(got.version, 1);
        assert!((got.score.unwrap() - 0.42).abs() < 1e-9);
    }

    #[tokio::test]
    async fn optional_fields_persist_none() {
        let repo = setup_repo().await;
        let rec = IdeaEvaluationRecord {
            id: "e1".to_string(),
            idea_id: "idea_a".to_string(),
            version: 1,
            ai_analysis: None,
            scores: None,
            score: None,
            evaluated_by: None,
            evaluated_at: "1700000000000".to_string(),
        };
        repo.insert(rec).await.unwrap();
        let got = repo.get_by_id("e1").await.unwrap().expect("记录存在");
        assert!(got.ai_analysis.is_none());
        assert!(got.scores.is_none());
        assert!(got.score.is_none());
        assert!(got.evaluated_by.is_none());
    }

    #[tokio::test]
    async fn update_full_rejected_for_append_only_audit() {
        // 加固(审计不可篡改):追加型审计表禁止 UPDATE。update_full 应返 Err 而非真执行
        // (防误用静默篡改历史快照,破坏版本单调性 + 审计追溯)。调用方应走 insert 追加新版本。
        let repo = setup_repo().await;
        repo.insert(erec("e1", "idea_a", 1, Some(0.5))).await.unwrap();
        // 试 update_full(篡改 version 1 → 2 + 改 score)
        let mut rec = repo.get_by_id("e1").await.unwrap().expect("记录存在");
        rec.version = 2;
        rec.score = Some(0.99);
        let res = repo.update_full(&rec).await;
        assert!(
            res.is_err(),
            "update_full 应被拒(追加型审计表禁止 UPDATE),实际: {:?}",
            res
        );
        // 原记录未被篡改(version 仍 1,score 仍 0.5)
        let got = repo.get_by_id("e1").await.unwrap().expect("记录存在");
        assert_eq!(got.version, 1, "原记录 version 不应被 update_full 篡改");
        assert!(
            (got.score.unwrap() - 0.5).abs() < 1e-9,
            "原记录 score 不应被 update_full 篡改"
        );
    }
}
