//! 任务推进节点 — advance_task 推进链触发器。
//!
//! 实现推进链的唯一 status 写入路径(D-260616-03 落 df-nodes Node):
//!   1. 读当前 TaskRecord(取 from status)
//!   2. 状态机校验 can_transition(from, to)(task_state_machine)
//!   3. 原子写:下沉 SQL `WHERE id AND status=expected` 防 TOCTOU,退回转换一并
//!      review_rounds+=1(见 df_storage::crud::TaskRepo::advance_status_atomic)
//!
//! 两条调用路径复用同一推进逻辑:
//!   - DAG 工作流:TaskAdvanceNode 实现 Node trait,从 NodeContext.config 读 task_id /
//!     target_status,持有 Arc<Database> 构造 TaskRepo。
//!   - IPC 直驱:advance_task_atomic 公开 async fn,src-tauri commands::task::advance_task
//!     thin 入口直接调用(无需构造 NodeContext,避免 EventBus/StateMachine 等重依赖)。

use std::sync::Arc;

use async_trait::async_trait;
use df_storage::crud::TaskRepo;
use df_storage::db::Database;
use df_storage::models::TaskRecord;
use df_workflow::node::{Node, NodeContext, NodeOutput, NodeResult, NodeSchema};

use crate::task_state_machine::{can_transition, is_regression, is_valid_state, ALL_STATES};

/// 推进任务到目标状态(核心逻辑,DAG 节点与 IPC 入口共用)。
///
/// 入参:
///   - `repo`:df-storage TaskRepo
///   - `id`:任务 ID
///   - `target_status`:目标状态(7 态之一,snake_case)
///
/// 流程:
///   1. 任务存在性:找不到 → NotFound 错误(前端可据此提示)
///   2. target_status 合法性:非 7 态 → Validation 错误(防脏数据直入推进链)
///   3. 状态机(三类拒绝,错误区分供前端分辨):
///      - 同态拒绝(from==to):Validation「相同状态,无需推进」(非状态机违例,是空操作)
///      - 非法转换(跳态/终态后继等):InvalidState「非法状态转换 X→Y」(含 from/to 上下文)
///      - can_transition 闸门矩阵判否即此分支
///   4. 原子写:advance_status_atomic CAS,to 是退回转换时 bump_rounds=true
///   5. CAS 失败(affected==0):状态已被并发改动 → InvalidState 错误(防 TOCTOU 静默成功)
///
/// 返回:成功推进后的最新 TaskRecord(含新 status / 累加后的 review_rounds / 新 updated_at)。
pub async fn advance_task_atomic(
    repo: &TaskRepo,
    id: &str,
    target_status: &str,
) -> df_types::error::Result<TaskRecord> {
    // 1. target 合法性(7 态之一)。先于读库校验:即便任务不存在,也先拒绝非法状态值。
    if !is_valid_state(target_status) {
        return Err(df_types::error::Error::Validation(format!(
            "非法 target_status {:?},合法值: {}",
            target_status,
            ALL_STATES.join("/")
        )));
    }

    // 2. 读当前任务(取 from status)。
    let current = repo
        .get_by_id(id)
        .await?
        .ok_or_else(|| df_types::error::Error::NotFound(format!("任务 {} 不存在", id)))?;

    // 3. 状态机校验(三类拒绝,错误类型区分供前端分辨):
    //    - 同态(from==to):Validation「相同状态,无需推进」(空操作,非状态机违例)
    //    - 非法转换(跳态/终态无后继等):InvalidState「非法状态转换 X→Y」
    let from = current.status.as_str();
    if from == target_status {
        return Err(df_types::error::Error::Validation(format!(
            "相同状态 {from:?},无需推进"
        )));
    }
    if !can_transition(from, target_status) {
        return Err(df_types::error::Error::InvalidState {
            current: format!("{from}→{target_status}(非法状态转换)"),
            expected: target_status.to_string(),
        });
    }

    // 4. 原子 CAS 写。退回转换一并 review_rounds+=1。
    let bump = is_regression(from, target_status);
    let updated = repo
        .advance_status_atomic(id, from, target_status, bump)
        .await?;

    // 5. CAS 失败:并发已改动 status(或任务被删),拒绝静默成功。
    updated.ok_or_else(|| df_types::error::Error::InvalidState {
        current: "已变更(并发推进或旁路修改)".to_string(),
        expected: from.to_string(),
    })
}

// ============================================================
// DAG 工作流节点 — TaskAdvanceNode(推进链在 DAG 内的形态)
// ============================================================

/// 任务推进节点 — DAG 工作流内触发 advance_task。
///
/// 持有 Arc<Database> 在 NodeRegistry 注册时注入(state.rs build_registry),
/// Node::execute 从 NodeContext.config 读 task_id / target_status,调 advance_task_atomic。
/// 当前推进链 F-01~04 阶段为手动推进(IPC 直驱),DAG 形态为阶段 2(工作流联动)预留。
pub struct TaskAdvanceNode {
    db: Arc<Database>,
}

impl TaskAdvanceNode {
    /// 构造节点(注册表工厂调用,注入数据库句柄)
    pub fn new(db: Arc<Database>) -> Self {
        Self { db }
    }
}

#[async_trait]
impl Node for TaskAdvanceNode {
    async fn execute(&self, ctx: NodeContext) -> NodeResult {
        let task_id = ctx
            .config
            .get("task_id")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("TaskAdvanceNode 缺少必填参数: task_id"))?;
        let target_status = ctx
            .config
            .get("target_status")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("TaskAdvanceNode 缺少必填参数: target_status"))?;

        let repo = TaskRepo::new(&self.db);
        let updated = advance_task_atomic(&repo, task_id, target_status)
            .await
            .map_err(|e| anyhow::anyhow!("推进任务失败: {}", e))?;

        Ok(NodeOutput::from_value(serde_json::json!({
            "task": updated,
            "task_id": updated.id,
            "status": updated.status,
            "review_rounds": updated.review_rounds,
        })))
    }

    fn schema(&self) -> NodeSchema {
        NodeSchema {
            params: serde_json::json!({
                "type": "object",
                "properties": {
                    "task_id": { "type": "string" },
                    "target_status": {
                        "type": "string",
                        "enum": ["todo", "in_progress", "in_review", "testing", "done", "blocked", "cancelled"]
                    }
                },
                "required": ["task_id", "target_status"]
            }),
            output: serde_json::json!({
                "type": "object",
                "properties": {
                    "task_id": { "type": "string" },
                    "status": { "type": "string" },
                    "review_rounds": { "type": "integer" }
                }
            }),
        }
    }

    fn node_type(&self) -> &str {
        "task_advance"
    }
}

// ============================================================
// 单元测试 — advance_task_atomic 状态机集成(内存 DB)
// ============================================================

#[cfg(test)]
mod tests {
    use super::*;
    use df_storage::crud::ProjectRepo;
    use df_storage::models::{ProjectRecord, TaskRecord};
    use df_types::types::{ProjectStatus, TaskStatus};

    fn rec(id: &str, status: TaskStatus) -> TaskRecord {
        TaskRecord {
            id: id.to_string(),
            project_id: "p1".to_string(),
            title: format!("t-{id}"),
            description: "".to_string(),
            status,
            priority: 2,
            branch_name: None,
            assignee: None,
            workflow_def_id: None,
            base_branch: None,
            review_rounds: 0,
            output_json: None,
            idea_id: None,
            queue: "todo".to_string(),
            parent_id: None,
            content_json: None,
            created_at: "0".to_string(),
            updated_at: "0".to_string(),
        }
    }

    async fn setup() -> TaskRepo {
        let db = Database::open_in_memory().await.expect("open_in_memory");
        // 先插父项目,满足 tasks.project_id FK 约束(PRAGMA foreign_keys=ON)。
        ProjectRepo::new(&db)
            .insert(ProjectRecord {
                id: "p1".to_string(),
                name: "proj".to_string(),
                description: "".to_string(),
                status: ProjectStatus::Planning,
                idea_id: None,
                path: None,
                stack: None,
                created_at: "0".to_string(),
                updated_at: "0".to_string(),
            })
            .await
            .expect("insert project");
        TaskRepo::new(&db)
    }

    #[tokio::test]
    async fn forward_path_todo_to_done() {
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::Todo)).await.unwrap();
        // 主路径逐级推进
        let r = advance_task_atomic(&repo, "t1", "in_progress").await.unwrap();
        assert_eq!(r.status.as_str(), "in_progress");
        assert_eq!(r.review_rounds, 0);
        let r = advance_task_atomic(&repo, "t1", "in_review").await.unwrap();
        assert_eq!(r.status.as_str(), "in_review");
        assert_eq!(r.review_rounds, 0);
        let r = advance_task_atomic(&repo, "t1", "testing").await.unwrap();
        assert_eq!(r.status.as_str(), "testing");
        let r = advance_task_atomic(&repo, "t1", "done").await.unwrap();
        assert_eq!(r.status.as_str(), "done");
        assert_eq!(r.review_rounds, 0);
    }

    #[tokio::test]
    async fn regression_in_review_to_in_progress_bumps_rounds() {
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::InReview)).await.unwrap();
        let r = advance_task_atomic(&repo, "t1", "in_progress").await.unwrap();
        assert_eq!(r.status.as_str(), "in_progress");
        assert_eq!(r.review_rounds, 1, "退回应 +1");
    }

    #[tokio::test]
    async fn regression_testing_to_in_review_bumps_rounds() {
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::Testing)).await.unwrap();
        let r = advance_task_atomic(&repo, "t1", "in_review").await.unwrap();
        assert_eq!(r.status.as_str(), "in_review");
        assert_eq!(r.review_rounds, 1, "退回应 +1");
    }

    #[tokio::test]
    async fn multiple_regressions_accumulate() {
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::InReview)).await.unwrap();
        // in_review → in_progress (+1) → in_review (前向,不动) → in_progress (+1=2)
        advance_task_atomic(&repo, "t1", "in_progress").await.unwrap();
        advance_task_atomic(&repo, "t1", "in_review").await.unwrap();
        let r = advance_task_atomic(&repo, "t1", "in_progress").await.unwrap();
        assert_eq!(r.review_rounds, 2);
    }

    #[tokio::test]
    async fn illegal_skip_rejected() {
        // CR-01-D: 非法转换(跳态)归 InvalidState,且消息含 from→to 上下文与「非法状态转换」。
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::Todo)).await.unwrap();
        let err = advance_task_atomic(&repo, "t1", "done").await.unwrap_err();
        match err {
            df_types::error::Error::InvalidState { current, expected } => {
                assert!(current.contains("todo→done"), "非法转换消息应含 from→to,实际: {current}");
                assert!(current.contains("非法状态转换"), "消息应含「非法状态转换」,实际: {current}");
                assert_eq!(expected, "done");
            }
            other => panic!("非法转换应是 InvalidState,实际: {other:?}"),
        }
    }

    #[tokio::test]
    async fn terminal_done_no_successor() {
        // CR-01-D: 终态无后继也是非法转换路径,归 InvalidState(同 illegal_skip_rejected)。
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::Done)).await.unwrap();
        let err = advance_task_atomic(&repo, "t1", "todo").await.unwrap_err();
        match err {
            df_types::error::Error::InvalidState { current, .. } => {
                assert!(current.contains("done→todo"), "终态后继消息应含 from→to,实际: {current}");
            }
            other => panic!("终态后继应是 InvalidState,实际: {other:?}"),
        }
    }

    #[tokio::test]
    async fn same_status_rejected() {
        // CR-01-D: 同态拒绝(from==to)与非法转换(跳态)错误类型区分。
        // 同态属空操作,归 Validation「相同状态,无需推进」;
        // 非法转换归 InvalidState「非法状态转换 X→Y」(见 illegal_skip_rejected)。
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::InProgress)).await.unwrap();
        let err = advance_task_atomic(&repo, "t1", "in_progress").await.unwrap_err();
        match err {
            df_types::error::Error::Validation(msg) => {
                assert!(msg.contains("相同状态"), "同态消息应含「相同状态」,实际: {msg}");
            }
            other => panic!("同态拒绝应是 Validation,实际: {other:?}"),
        }
    }

    #[tokio::test]
    async fn invalid_target_rejected() {
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::Todo)).await.unwrap();
        let err = advance_task_atomic(&repo, "t1", "merged").await.unwrap_err();
        assert!(matches!(err, df_types::error::Error::Validation(_)));
    }

    #[tokio::test]
    async fn not_found_rejected() {
        let repo = setup().await;
        let err = advance_task_atomic(&repo, "nope", "in_progress").await.unwrap_err();
        assert!(matches!(err, df_types::error::Error::NotFound(_)));
    }

    #[tokio::test]
    async fn advance_succeeds_after_prior_status_change() {
        // 前序旁路改 status 后,推进链基于「当前」status 判定合法转换并正常写入。
        // 注:这并非 CAS 并发失败场景(真 CAS 失败由 cas_returns_none_when_status_mismatch 覆盖),
        // 而是验证读后改路径在 from=当前库内 status 时正常推进。
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::Todo)).await.unwrap();
        // 另一路推进把 status 改成 in_progress(模拟并发推进,走 CAS 合法路径;
        // F-03 收口后 status 不在 update_field 白名单,模拟并发改态须走 advance_status_atomic)
        repo.advance_status_atomic("t1", "todo", "in_progress", false)
            .await
            .unwrap();
        // 读出来是 in_progress,推进到 in_review 合法 → 正常成功
        let r = advance_task_atomic(&repo, "t1", "in_review").await.unwrap();
        assert_eq!(r.status.as_str(), "in_review");
    }

    #[tokio::test]
    async fn cas_returns_none_when_status_mismatch() {
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::Todo)).await.unwrap();
        // 直接调底层:expected 传错(模拟读到 todo 但实际已被改成 in_progress)
        let r = repo
            .advance_status_atomic("t1", "todo", "in_review", false)
            .await
            .unwrap();
        // 此时 status 确实是 todo,CAS 成功,返回 Some
        assert!(r.is_some());
        // 再用错误的 expected todo(实际已是 in_review)→ CAS 失败 None
        let r = repo
            .advance_status_atomic("t1", "todo", "done", false)
            .await
            .unwrap();
        assert!(r.is_none(), "expected 与库内不符应 CAS 失败");
    }

    #[tokio::test]
    async fn blocked_round_trip_does_not_bump() {
        let repo = setup().await;
        repo.insert(rec("t1", TaskStatus::InProgress)).await.unwrap();
        let r = advance_task_atomic(&repo, "t1", "blocked").await.unwrap();
        assert_eq!(r.status.as_str(), "blocked");
        assert_eq!(r.review_rounds, 0, "进 blocked 不累加");
        let r = advance_task_atomic(&repo, "t1", "in_progress").await.unwrap();
        assert_eq!(r.status.as_str(), "in_progress");
        assert_eq!(r.review_rounds, 0, "解除 blocked 不累加");
    }

    // ============================================================
    // CR-06 观察项 ② 测试补强 — ②-3(completed)/②-4(failed) 回调推进覆盖
    //
    // workflow.rs 的 spawn 闭包回调依赖三段纯计算:
    //   1. template_for(target) 选模板(已由 task_workflow_templates.rs 测试覆盖)
    //   2. regression_target(target) 推失败退回态(已收敛到 task_state_machine::regression_target
    //      pub 契约,workflow.rs 与本测试均从此导入,单一可信源;映射表由 task_state_machine
    //      测试模块锁定)
    //   3. advance_task_atomic(repo, tid, to) 落库(本模块核心,下方覆盖各回调 to)
    //
    // workflow.rs spawn 闭包集成测试需 mock AppState/AppHandle/EventBus,df-nodes crate
    // 无此基础设施 → 降级为纯函数级 + 状态机集成级(零 mock,稳)。回调三段计算全覆盖,
    // 唯一未覆盖的是「闭包组装 + tracing」胶水代码(非业务逻辑)。
    // ============================================================

    /// 镜像 workflow.rs spawn 闭包的回调决策(completed→target / failed→regression_target)。
    /// 抽出纯函数便于直接断言,与 workflow.rs 闭包内 match 逻辑一一对应。
    ///
    /// regression_target 现已收敛为 df-nodes::task_state_machine::regression_target 的
    /// pub 契约(单一可信源),此处直接复用,不再镜像副本(防 DRY 漂移)。
    ///
    /// 返回 Option<String>(非 &str):completed 分支回传入的 target_status(非 'static),
    /// failed 分支回 regression_target 的 &'static str 常量。统一 String 避免生命周期冲突。
    fn callback_advance_target(status: &str, target_status: &str) -> Option<String> {
        match status {
            "completed" => Some(target_status.to_string()),
            "failed" => crate::task_state_machine::regression_target(target_status)
                .map(String::from),
            _ => None,
        }
    }

    #[test]
    fn callback_completed_advances_to_target() {
        // ②-3:工作流 completed → 推进到 target_status(无论 target 是哪个)
        assert_eq!(
            callback_advance_target("completed", "in_progress").as_deref(),
            Some("in_progress")
        );
        assert_eq!(
            callback_advance_target("completed", "testing").as_deref(),
            Some("testing")
        );
        assert_eq!(
            callback_advance_target("completed", "done").as_deref(),
            Some("done")
        );
    }

    #[test]
    fn callback_failed_advances_to_regression_target() {
        // ②-4:工作流 failed → 推进到 regression_target(target)
        assert_eq!(
            callback_advance_target("failed", "testing").as_deref(),
            Some("in_review")
        );
        assert_eq!(
            callback_advance_target("failed", "in_review").as_deref(),
            Some("in_progress")
        );
        // CR-13-O1-b: in_progress 失败不退回(状态机禁止→todo),回调返回 None 跳过推进
        assert_eq!(callback_advance_target("failed", "in_progress"), None);
        // failed + done:无退回映射 → None(回调跳过,workflow.rs 会 warn 跳过日志)
        assert_eq!(callback_advance_target("failed", "done"), None);
    }

    #[test]
    fn callback_non_terminal_status_skips() {
        // 工作流非终态(running/cancelled 等)不应触发回调 — workflow.rs match 的 _ 分支。
        // (cancelled 在 workflow.rs 实际归入 failed 路径因 status 字段取值,这里锁定
        //  唯一终态语义:仅 "completed"/"failed" 两态触发推进。)
        for non_terminal in ["running", "pending", ""] {
            assert_eq!(
                callback_advance_target(non_terminal, "in_progress"),
                None,
                "非终态 {non_terminal:?} 不应触发回调推进"
            );
        }
    }

    /// ②-3 回调落库验证:completed → advance_task_atomic(target) 成功推进 + 不累加 rounds。
    /// 覆盖三个 target 的前向推进(in_progress/testing/done)从各自合法 from 态触发。
    #[tokio::test]
    async fn callback_completed_advance_lands_in_db() {
        // in_progress: todo → in_progress(②-3 in_progress 模板完成)
        let repo = setup().await;
        repo.insert(rec("c1", TaskStatus::Todo)).await.unwrap();
        let to = callback_advance_target("completed", "in_progress").unwrap();
        let r = advance_task_atomic(&repo, "c1", &to).await.unwrap();
        assert_eq!(r.status.as_str(), "in_progress");
        assert_eq!(r.review_rounds, 0, "②-3 前向推进不累加 review_rounds");

        // testing: in_review → testing(②-3 testing 模板自审+核对通过)
        repo.insert(rec("c2", TaskStatus::InReview)).await.unwrap();
        let to = callback_advance_target("completed", "testing").unwrap();
        let r = advance_task_atomic(&repo, "c2", &to).await.unwrap();
        assert_eq!(r.status.as_str(), "testing");
        assert_eq!(r.review_rounds, 0);

        // done: testing → done(②-3 done 模板最终核对通过)
        repo.insert(rec("c3", TaskStatus::Testing)).await.unwrap();
        let to = callback_advance_target("completed", "done").unwrap();
        let r = advance_task_atomic(&repo, "c3", &to).await.unwrap();
        assert_eq!(r.status.as_str(), "done");
        assert_eq!(r.review_rounds, 0);
    }

    /// ②-4 回调落库验证:failed → advance_task_atomic(regression_target) 退回 + 累加 rounds。
    /// 关键差异:②-4 退回转换是 regression,review_rounds += 1(与 ②-3 前向推进 0 增量对比)。
    #[tokio::test]
    async fn callback_failed_regression_lands_in_db_with_rounds_bump() {
        // testing 模板失败:任务当前 testing → 退回 in_review(rounds+1)
        let repo = setup().await;
        repo.insert(rec("f1", TaskStatus::Testing)).await.unwrap();
        let to = callback_advance_target("failed", "testing").unwrap();
        assert_eq!(to, "in_review", "testing 失败应退回 in_review");
        let r = advance_task_atomic(&repo, "f1", &to).await.unwrap();
        assert_eq!(r.status.as_str(), "in_review");
        assert_eq!(r.review_rounds, 1, "②-4 退回应累加 review_rounds(+1)");

        // in_review 模板失败(注:in_review 非 callback target,但映射存在性仍锁定):
        // in_review → in_progress。此处验证 regression_target 对 in_review 的映射,
        // 即便当前推进链 testing 模板失败也可能退到 in_progress(链式退回)。
        repo.insert(rec("f2", TaskStatus::InReview)).await.unwrap();
        let to = callback_advance_target("failed", "in_review").unwrap();
        assert_eq!(to, "in_progress");
        let r = advance_task_atomic(&repo, "f2", &to).await.unwrap();
        assert_eq!(r.status.as_str(), "in_progress");
        assert_eq!(r.review_rounds, 1);

        // in_progress 模板失败:regression_target("in_progress")=None(CR-13-O1-b),
        // 回调返回 None → 跳过推进,任务保留 in_progress 等人介入。
        // 验证:callback 返回 None,不调 advance_task_atomic。
        repo.insert(rec("f3", TaskStatus::InProgress)).await.unwrap();
        let to = callback_advance_target("failed", "in_progress");
        assert_eq!(to, None, "in_progress 失败应跳过推进(None),实际: {to:?}");
        // 任务状态未被改动(仍是 in_progress)
        let still = repo.get_by_id("f3").await.unwrap().unwrap();
        assert_eq!(still.status.as_str(), "in_progress", "None 退回不应改动 status");
    }

    /// ②-4 回调 failed+done 无退回映射验证:callback_advance_target 返回 None。
    /// workflow.rs 此分支仅 tracing::warn! 跳过,不调 advance_task_atomic。
    /// 锁定:done 失败不会推进任务(终态前向目标失败,无可退态)。
    #[tokio::test]
    async fn callback_failed_done_skips_advance() {
        let to = callback_advance_target("failed", "done");
        assert_eq!(to, None, "done 失败无退回映射 → 回调跳过(None)");
        // 不调 advance_task_atomic(workflow.rs L294-302 的 else 分支)
    }
}
