//! 任务域 Repo:TaskRepo(含 advance_status_atomic 状态机收口)

use std::sync::Arc;

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

use df_types::error::Result;

use crate::db::Database;
use crate::models::TaskRecord;

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

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

fn task_from_row(row: &Row<'_>) -> std::result::Result<TaskRecord, rusqlite::Error> {
    Ok(TaskRecord {
        id: row.get("id")?,
        project_id: row.get("project_id")?,
        title: row.get("title")?,
        description: row.get("description")?,
        status: row.get("status")?,
        priority: row.get("priority")?,
        branch_name: row.get("branch_name")?,
        assignee: row.get("assignee")?,
        workflow_def_id: row.get("workflow_def_id")?,
        base_branch: row.get("base_branch")?,
        review_rounds: row.get("review_rounds")?,
        output_json: row.get("output_json")?,
        idea_id: row.get("idea_id")?,
        created_at: row.get("created_at")?,
        updated_at: row.get("updated_at")?,
    })
}

// ============================================================
// 任务列表查询入参(F-260621-02 查询维度补全)
// ============================================================

/// 任务列表动态查询入参。全可选,空 query = 等价当前全量行为(向后兼容)。
///
/// 设计对齐方案文档(docs/02-架构设计/专项设计/查询能力补全方案-2026-06-21.md §4.1):
/// ① 字段全 Option,旧调用方不传 / 传空 → 等价 list_active(全量未删),零破坏;
/// ② 复用 KnowledgeRepo::search 的动态 WHERE 拼接模式(if-let 分支拼 SQL + 参数绑定);
/// ③ order_by 走白名单(TASK_ORDER_BY_WHITELIST)防 SQL 注入,对齐
///    impl_repo! 宏 validate_column_name 的白名单防注入思路;
/// ④ limit/offset 钳制上限(limit.min(500))防滥用,对齐 conversation_repo::list_recent
///    的 limit.min(200)(任务场景放宽至 500,数据量 ~15 任务)。
///
/// 当前仅 status(P1 下沉)+ keyword(P2 LIKE)在视图链路使用;project_id/priority/assignee/
/// order_by/limit/offset 为基建就绪(视图暂不用,P3 排序分页待数据量增长)。
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TaskQuery {
    /// 项目 ID 过滤(SQL 下推,命中 idx_tasks_project_id)
    pub project_id: Option<String>,
    /// 状态过滤(P1 下沉,命中 idx_tasks_status)。值集由上层 TaskStatus::is_valid 兜底校验。
    pub status: Option<String>,
    /// 优先级过滤(0=critical..3=low)。P3 基建就绪,视图暂不用。
    pub priority: Option<i32>,
    /// 负责人过滤。P3 基建就绪,视图暂不用。
    pub assignee: Option<String>,
    /// 关键词搜索(P2):title/description LIKE %kw%,对齐知识库 search 的 LIKE 模式。
    pub keyword: Option<String>,
    /// 排序字段(白名单 created_at/updated_at/priority/status,降序)。P3 基建就绪。
    pub order_by: Option<String>,
    /// 分页上限(钳制 ≤500)。P3 基建就绪。
    pub limit: Option<u32>,
    /// 分页偏移。P3 基建就绪。
    pub offset: Option<u32>,
}

/// order_by 白名单:只允许这些列名拼进 SQL(防注入,列名不可参数化只能白名单)。
/// 对齐 impl_repo! 宏 validate_column_name 的「白名单先于拼接校验」防注入思路。
/// 注:created_at 是默认值(白名单含它,query 未指定时用 created_at DESC)。
const TASK_ORDER_BY_WHITELIST: &[&str] = &["created_at", "updated_at", "priority", "status"];

/// 校验 order_by 列名在白名单内,否则返回 Err(防 SQL 注入)。
fn validate_order_by(field: &str) -> Result<()> {
    if TASK_ORDER_BY_WHITELIST.contains(&field) {
        Ok(())
    } else {
        Err(df_types::error::Error::Storage(format!(
            "非法 order_by 字段名: {}，合法值: {:?}",
            field, TASK_ORDER_BY_WHITELIST
        )))
    }
}

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

impl_repo!(
    /// 任务表 CRUD
    TaskRepo,
    TaskRecord,
    "tasks",
    from_row => |row| task_from_row(row),
    insert  => |conn, rec| {
        conn.execute(
            "INSERT INTO tasks (id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, idea_id, created_at, updated_at)
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)",
            params![
                rec.id, rec.project_id, rec.title, rec.description, rec.status, rec.priority,
                rec.branch_name, rec.assignee, rec.workflow_def_id, rec.base_branch,
                rec.review_rounds, rec.output_json, rec.idea_id, rec.created_at, rec.updated_at
            ],
        )
    },
    update  => |conn, rec| {
        conn.execute(
            "UPDATE tasks SET project_id = ?1, title = ?2, description = ?3, status = ?4, priority = ?5, branch_name = ?6, assignee = ?7, workflow_def_id = ?8, base_branch = ?9, review_rounds = ?10, output_json = ?11, idea_id = ?12, updated_at = ?13 WHERE id = ?14",
            params![
                rec.project_id, rec.title, rec.description, rec.status, rec.priority,
                rec.branch_name, rec.assignee, rec.workflow_def_id, rec.base_branch,
                rec.review_rounds, rec.output_json, rec.idea_id, rec.updated_at, rec.id
            ],
        )
    }
);

impl TaskRepo {
    /// 列出未删除任务(deleted_at IS NULL)— 对标 ProjectRepo::list_active
    ///
    /// 显式列出全部 15 个 TaskRecord 列名(同 ProjectRepo::list_active 写法),
    /// 不 SELECT deleted_at:TaskRecord 不带该字段,取了 from_row 会因未知列报错。
    pub async fn list_active(&self) -> Result<Vec<TaskRecord>> {
        let conn = self.conn.clone();
        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();
            let mut stmt = guard
                .prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, idea_id, created_at, updated_at FROM tasks WHERE deleted_at IS NULL ORDER BY created_at DESC")
                .map_err(storage_err)?;
            let rows = stmt
                .query_map([], |row| task_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)?
    }

    /// 软删:标记 deleted_at(进回收站,可恢复)。仅作用于未删任务,返回是否命中。
    /// 对标 ProjectRepo::soft_delete。
    pub async fn soft_delete(&self, id: &str) -> Result<bool> {
        let conn = self.conn.clone();
        let id = id.to_owned();
        let now = now_millis_str();
        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();
            let affected = guard
                .execute(
                    "UPDATE tasks SET deleted_at = ?1, updated_at = ?1 WHERE id = ?2 AND deleted_at IS NULL",
                    params![now, id],
                )
                .map_err(storage_err)?;
            Ok(affected > 0)
        })
        .await
        .map_err(storage_err)?
    }

    /// 恢复:清 deleted_at(从回收站还原)。仅作用于已删任务,返回是否命中。
    /// 对标 ProjectRepo::restore。
    pub async fn restore(&self, id: &str) -> Result<bool> {
        let conn = self.conn.clone();
        let id = id.to_owned();
        let now = now_millis_str();
        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();
            let affected = guard
                .execute(
                    "UPDATE tasks SET deleted_at = NULL, updated_at = ?1 WHERE id = ?2 AND deleted_at IS NOT NULL",
                    params![now, id],
                )
                .map_err(storage_err)?;
            Ok(affected > 0)
        })
        .await
        .map_err(storage_err)?
    }

    /// 原子推进任务状态(任务推进链 F-260616-02 唯一 status 写入路径)
    ///
    /// 下沉 SQL `WHERE id=? AND status=?expected` 做 CAS(Compare-And-Swap)防 TOCTOU:
    /// 并发推进/旁路修改若已改 status,affected_rows==0,本方法返回 None,调用方
    /// (task_advance_node)据此报「状态已变,推进中止」。`review_rounds` 不进通用
    /// update_field 白名单(收口:仅本方法可改 status 与 review_rounds)。
    ///
    /// - `expected`:调用方读取的当前 status(状态机校验时的 from),CAS 前置。
    /// - `new_status`:目标 status(状态机 can_transition 已校验合法)。
    /// - `bump_rounds`:退回转换(in_review→in_progress / testing→in_review)传 true,
    ///   一并 `review_rounds = review_rounds + 1`(同 UPDATE 原子,避免读改写竞争)。
    ///   前向推进 / 进出 blocked / 进 cancelled 传 false,不动 review_rounds。
    ///
    /// 返回:成功推进返回更新后的 TaskRecord;affected==0(状态已变/任务不存在)返回 None。
    pub async fn advance_status_atomic(
        &self,
        id: &str,
        expected: &str,
        new_status: &str,
        bump_rounds: bool,
    ) -> Result<Option<TaskRecord>> {
        let conn = self.conn.clone();
        let id = id.to_owned();
        let expected = expected.to_owned();
        let new_status = new_status.to_owned();
        let now = now_millis_str();
        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();
            // CAS:WHERE id AND status=expected 锁定当前态;affected==0 即并发已改动。
            // deleted_at IS NULL:回收站任务(soft_delete 设了 deleted_at)CAS 必败→affected=0,
            // 返回 None,杜绝回收站任务被推进(D-02 软删语义收口,一处关闭)。
            let sql = if bump_rounds {
                "UPDATE tasks SET status = ?1, review_rounds = review_rounds + 1, updated_at = ?2 \
                 WHERE id = ?3 AND status = ?4 AND deleted_at IS NULL"
            } else {
                "UPDATE tasks SET status = ?1, updated_at = ?2 \
                 WHERE id = ?3 AND status = ?4 AND deleted_at IS NULL"
            };
            let affected = guard
                .execute(sql, params![new_status, now, id, expected])
                .map_err(storage_err)?;
            if affected == 0 {
                return Ok(None);
            }
            // 回读更新后的记录(含新 status / 累加后的 review_rounds / 新 updated_at)。
            let mut stmt = guard
                .prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, idea_id, created_at, updated_at FROM tasks WHERE id = ?1")
                .map_err(storage_err)?;
            let row = stmt
                .query_row(params![id], |row| task_from_row(row))
                .optional()
                .map_err(storage_err)?;
            Ok(row)
        })
        .await
        .map_err(storage_err)?
    }

    /// 按项目列出未删除任务(deleted_at IS NULL AND project_id = ?),按创建时间降序。
    ///
    /// SQL 下推 project_id 过滤:替代旧 list_active + 内存 retain 全表扫(任务量增长后
    /// N×M 热点,每页都拉全表进内存再丢)。list_tasks 在有 project_id 时优先走本方法。
    pub async fn list_active_by_project(&self, project_id: &str) -> Result<Vec<TaskRecord>> {
        let conn = self.conn.clone();
        let pid = project_id.to_owned();
        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();
            let mut stmt = guard
                .prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, idea_id, created_at, updated_at FROM tasks WHERE deleted_at IS NULL AND project_id = ?1 ORDER BY created_at DESC")
                .map_err(storage_err)?;
            let rows = stmt
                .query_map(params![pid], |row| task_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)?
    }

    /// 动态条件列出未删除任务(F-260621-02 查询维度补全)。
    ///
    /// 复用 KnowledgeRepo::search 的「动态 WHERE + 参数绑定」模式,但用累积式条件收集
    /// (Vec<String> WHERE 子句 + Vec<rusqlite::Value> 参数)替代 if-let 二分支——
    /// TaskQuery 有 4 个过滤维度(project_id/status/priority/assignee/keyword),2^n 分支不可行,
    /// 累积式天然支持任意维度组合,且每个 if-let 分支只 push 子句+参数,新增维度零样板。
    ///
    /// - 过滤维度:project_id / status / priority / assignee(精确等值)+ keyword(title/description LIKE)
    /// - keyword 拼成 `(title LIKE ?N OR description LIKE ?M)`,pattern = `%kw%`(对齐知识库 search)
    /// - order_by 白名单(validate_order_by 防 SQL 注入),默认 created_at,恒 DESC(与 list_active 一致)
    /// - limit/offset 钳制(limit ≤500 防滥用,对齐 conversation_repo::list_recent 的 limit≤200 思路)
    /// - deleted_at IS NULL 恒带(回收站任务不进结果,语义同 list_active,不可被 query 关闭)
    /// - 显式列出全部 15 列(不 SELECT deleted_at:TaskRecord 不带该字段,取了 from_row 报未知列)
    ///
    /// 空 query(全 None)→ 等价 list_active(全量未删,created_at DESC),向后兼容。
    /// status 值合法性由上层 list_tasks 命令(TaskStatus::is_valid)兜底,本层不过滤值集
    /// (非法 status 在 DB 无匹配行,返回空 Vec,无害)。
    pub async fn list_by_query(&self, query: &TaskQuery) -> Result<Vec<TaskRecord>> {
        let conn = self.conn.clone();

        // order_by 白名单校验在闭包外做(提前 fail-fast,非法值不进 DB 层)。列名不可
        // 参数化,只能白名单防注入(对齐 impl_repo! 宏 validate_column_name 思路)。
        // order_col 转 String 拥有所有权:避免 &str 借用 query 跨 spawn_blocking 'static
        // 闭包(E0521 borrowed data escapes)。
        let order_col = query.order_by.as_deref().unwrap_or("created_at");
        validate_order_by(order_col)?;
        let order_col = order_col.to_string();

        // limit 钳制上限 500 防滥用(对齐 conversation_repo::list_recent 的 limit.min(200)
        // 思路,任务场景放宽;None → 不拼 LIMIT = 全量,语义同 list_active)。
        let limit_i: Option<i64> = query.limit.map(|l| (l.min(500)) as i64);
        let offset_i: i64 = query.offset.unwrap_or(0) as i64;

        // 拷贝 query 字段进闭包('static 生命周期,spawn_blocking 要求)
        let project_id = query.project_id.clone();
        let status = query.status.clone();
        let priority = query.priority;
        let assignee = query.assignee.clone();
        let keyword = query.keyword.clone();

        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();

            // ── 累积 WHERE 子句 + 收集参数(按出现顺序绑定占位符 ?N,序号 = 已有子句数+1)──
            // deleted_at IS NULL 恒带(常量条件无占位符),回收站任务不进结果(语义同 list_active)。
            let mut where_clauses: Vec<String> = vec!["deleted_at IS NULL".to_string()];
            let mut params_vec: Vec<Box<dyn rusqlite::ToSql>> = Vec::new();

            if let Some(pid) = &project_id {
                where_clauses.push(format!("project_id = ?{}", where_clauses.len() + 1));
                params_vec.push(Box::new(pid.clone()));
            }
            if let Some(s) = &status {
                where_clauses.push(format!("status = ?{}", where_clauses.len() + 1));
                params_vec.push(Box::new(s.clone()));
            }
            if let Some(p) = priority {
                where_clauses.push(format!("priority = ?{}", where_clauses.len() + 1));
                params_vec.push(Box::new(p));
            }
            if let Some(a) = &assignee {
                where_clauses.push(format!("assignee = ?{}", where_clauses.len() + 1));
                params_vec.push(Box::new(a.clone()));
            }
            // keyword: title/description LIKE %kw%(P2,对齐知识库 search 的 LIKE 模式)
            if let Some(kw) = &keyword {
                let pat = format!("%{kw}%");
                let p1 = where_clauses.len() + 1;
                let p2 = p1 + 1;
                where_clauses.push(format!("(title LIKE ?{p1} OR description LIKE ?{p2})"));
                params_vec.push(Box::new(pat.clone()));
                params_vec.push(Box::new(pat));
            }

            // LIMIT/OFFSET:limit 为 None → 不拼(全量);有 limit 时 offset 紧跟其后。
            let where_param_count = params_vec.len();
            let limit_sql_bound = match limit_i {
                Some(_) => format!(
                    " LIMIT ?{} OFFSET ?{}",
                    where_param_count + 1,
                    where_param_count + 2
                ),
                None => String::new(),
            };

            // 拼 SQL:?N 占位符序号与 params_vec 顺序严格对应(累积时按 +1 递增保证)。
            let sql = format!(
                "SELECT id, project_id, title, description, status, priority, branch_name, \
                 assignee, workflow_def_id, base_branch, review_rounds, output_json, idea_id, \
                 created_at, updated_at FROM tasks WHERE {} ORDER BY {} DESC{}",
                where_clauses.join(" AND "),
                order_col,
                limit_sql_bound
            );

            let mut stmt = guard.prepare(&sql).map_err(storage_err)?;

            // 组装参数引用数组(where 参数 + 可选 limit/offset)。
            // limit/offset 也压入 params_vec 收口:借用引用需指向同一生命周期存活处,
            // 收口到 params_vec 后再统一取引用,避免局部 l 生命周期不足(E0597,
            // 对齐 idea_repo.rs list_by_query 同名模式)。
            if let Some(l) = limit_i {
                params_vec.push(Box::new(l));
                params_vec.push(Box::new(offset_i));
            }
            let param_refs: Vec<&dyn rusqlite::ToSql> =
                params_vec.iter().map(|p| p.as_ref()).collect();

            let rows = stmt
                .query_map(param_refs.as_slice(), |row| task_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)?
    }

    /// 列出回收站(deleted_at IS NOT NULL),按更新时间(≈删除时间)降序。对标 ProjectRepo::list_deleted。
    ///
    /// 注:按项目列活跃任务走 list_active_by_project(SQL 下推 project_id),
    /// 无 pid 时 fallback list_active。
    pub async fn list_deleted(&self) -> Result<Vec<TaskRecord>> {
        let conn = self.conn.clone();
        tokio::task::spawn_blocking(move || {
            let guard = conn.blocking_lock();
            let mut stmt = guard
                .prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, idea_id, created_at, updated_at FROM tasks WHERE deleted_at IS NOT NULL ORDER BY updated_at DESC")
                .map_err(storage_err)?;
            let rows = stmt
                .query_map([], |row| task_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)?
    }
}
