//! DagExecutor 测试套件 — 从 executor.rs 抽离(纯搬迁,零行为变更)
//!
//! 抽离说明:executor.rs 主体 run 逻辑之外曾带大量内联测试,占比偏高(抽离前的历史
//! 行数分布不再维护),独立成文件降低阅读 run 主体的滚动噪音。测试逻辑、断言、节点 mock
//! 全部原样搬迁。
//! 通过 `#[path = "executor_helpers.rs"]` 内联回 executor.rs 的 #[cfg(test)] mod,
//! 不进 lib.rs、不改 pub 路径、不影响外部调用方。
//!
//! SW-01 TOCTOU 相关测试(test_cancelled_node_skips_set_failed /
//! test_cancelled_node_skips_set_completed / test_cancelled_node_emits_node_cancelled_event)
//! 原样保留,锁定 executor.rs 节点执行完毕后 Ok/Err 两分支的 is_cancelled 短路行为
//! (已取消节点跳过 set_completed/set_failed,保持 Cancelled 终态)。

use super::*;
use crate::node::{Node, NodeResult, NodeSchema};
use async_trait::async_trait;
use std::time::Duration;

/// 测试节点：sleep 指定毫秒后返回空输出
struct SleepNode {
    sleep_ms: u64,
}

#[async_trait]
impl Node for SleepNode {
    async fn execute(&self, _ctx: NodeContext) -> NodeResult {
        tokio::time::sleep(Duration::from_millis(self.sleep_ms)).await;
        Ok(NodeOutput::empty())
    }

    fn schema(&self) -> NodeSchema {
        NodeSchema {
            params: serde_json::Value::Null,
            output: serde_json::Value::Null,
        }
    }

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

/// 测试节点：直接返回错误
struct FailNode;

/// 测试节点：把 ctx.config 的指定 key 字符串原样回显到 output（验证节点级 config 下沉）
struct EchoConfigNode {
    key: String,
}

#[async_trait]
impl Node for EchoConfigNode {
    async fn execute(&self, ctx: NodeContext) -> NodeResult {
        let v = ctx
            .config
            .get(&self.key)
            .and_then(|v| v.as_str())
            .unwrap_or("")
            .to_string();
        Ok(NodeOutput::from_value(serde_json::json!({ "echo": v })))
    }

    fn schema(&self) -> NodeSchema {
        NodeSchema {
            params: serde_json::Value::Null,
            output: serde_json::Value::Null,
        }
    }

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

#[async_trait]
impl Node for FailNode {
    async fn execute(&self, _ctx: NodeContext) -> NodeResult {
        Err(anyhow::anyhow!("故意失败"))
    }

    fn schema(&self) -> NodeSchema {
        NodeSchema {
            params: serde_json::Value::Null,
            output: serde_json::Value::Null,
        }
    }

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

/// ④-1:节点级 config 下沉验证。
/// DagDef 节点 config={foo:"bar"} + 全局 config={foo:"GLOBAL"} → NodeContext.config.foo=="bar"
/// （节点级覆盖全局级）。同时验证缺节点 config 的节点回退全局 config（零行为破坏现有调用方）。
#[tokio::test]
async fn node_config_overrides_global_in_node_context() {
    use crate::registry::NodeRegistry;

    // 构造 DagDef：n1 节点写 config={foo:"bar"}；n2 节点 config 空（验证回退全局）
    let mut def = crate::dag_def::DagDef::new();
    def.add_node("n1".to_string(), "echo".to_string(), serde_json::json!({ "foo": "bar" }));
    def.add_node("n2".to_string(), "echo".to_string(), serde_json::json!({}));

    // 注册 echo 工厂 → EchoConfigNode（读 config.foo）
    let mut registry = NodeRegistry::new();
    registry.register("echo", |_cfg| {
        Box::new(EchoConfigNode { key: "foo".to_string() })
    });

    let dag = registry.build_dag(&def).expect("build_dag");

    let mut executor = DagExecutor::new(EventBus::new(), "test-nodecfg".into());
    // 全局 config 写 foo=GLOBAL（验证被 n1 节点级覆盖，n2 回退用全局）
    let outputs = executor
        .run(&dag, serde_json::json!({ "foo": "GLOBAL" }))
        .await
        .expect("run");

    // n1:节点级 foo="bar" 覆盖全局 "GLOBAL"
    assert_eq!(
        outputs["n1"].data["echo"],
        serde_json::json!("bar"),
        "节点级 config 应覆盖全局"
    );
    // n2:节点 config 空 → 回退全局 foo="GLOBAL"（零行为破坏现有调用方）
    assert_eq!(
        outputs["n2"].data["echo"],
        serde_json::json!("GLOBAL"),
        "空节点 config 应回退全局"
    );
}

#[tokio::test]
async fn test_same_layer_runs_in_parallel() {
    // 两个无依赖节点位于同一层，各 sleep 100ms
    let mut dag = Dag::new();
    dag.add_node("a".to_string(), Box::new(SleepNode { sleep_ms: 100 }));
    dag.add_node("b".to_string(), Box::new(SleepNode { sleep_ms: 100 }));

    let mut executor = DagExecutor::new(EventBus::new(), "test-exec".into());
    let start = std::time::Instant::now();
    let outputs = executor.run(&dag, serde_json::Value::Null).await.unwrap();
    let elapsed = start.elapsed();

    assert_eq!(outputs.len(), 2);
    // 串行需要约 200ms，并行应明显小于 180ms
    assert!(
        elapsed < Duration::from_millis(180),
        "同层节点应并行执行，实际耗时 {:?}",
        elapsed
    );
}

#[tokio::test]
async fn test_layer_failure_aborts_following_layers() {
    // a(失败) 与 b(成功) 同层，c 依赖 a，失败后 c 不应执行
    let mut dag = Dag::new();
    dag.add_node("a".to_string(), Box::new(FailNode));
    dag.add_node("b".to_string(), Box::new(SleepNode { sleep_ms: 10 }));
    dag.add_node("c".to_string(), Box::new(SleepNode { sleep_ms: 10 }));
    dag.add_edge("a".to_string(), "c".to_string());

    let mut executor = DagExecutor::new(EventBus::new(), "test-exec".into());
    let err = executor
        .run(&dag, serde_json::Value::Null)
        .await
        .unwrap_err();
    assert!(err.to_string().contains("节点 a 执行失败"));

    // 同层成功节点状态正常更新，下游节点保持 Pending
    use df_types::types::NodeStatus;
    assert_eq!(executor.state_machine.get(&"a".to_string()), NodeStatus::Failed);
    assert_eq!(executor.state_machine.get(&"b".to_string()), NodeStatus::Completed);
    assert_eq!(executor.state_machine.get(&"c".to_string()), NodeStatus::Pending);
}

/// 测试节点:执行中通过共享 node_status 自取消(模拟外部 IPC set_cancelled),返回 Err
struct CancelSelfNode;

#[async_trait]
impl Node for CancelSelfNode {
    async fn execute(&self, ctx: NodeContext) -> NodeResult {
        // 通过共享 node_status 置 Cancelled(写共享 HashMap,executor.state_machine 可见)
        ctx.node_status.set_cancelled(ctx.node_id.clone());
        Err(anyhow::anyhow!("人工审批被取消"))
    }

    fn schema(&self) -> NodeSchema {
        NodeSchema {
            params: serde_json::Value::Null,
            output: serde_json::Value::Null,
        }
    }

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

/// B-03b-R1:取消的节点返回 Err 时,executor 跳过 set_failed(Cancelled→Failed transition 非法会 bail),
/// 状态保持 Cancelled,run 返回取消相关 Err 而非状态转换错误。
#[tokio::test]
async fn test_cancelled_node_skips_set_failed() {
    use df_types::types::NodeStatus;
    let mut dag = Dag::new();
    dag.add_node("x".to_string(), Box::new(CancelSelfNode));

    let mut executor = DagExecutor::new(EventBus::new(), "test-cancel".into());
    let result = executor.run(&dag, serde_json::Value::Null).await;

    // run 返回 Err(取消致中止后续层),但不 panic/bail transition
    assert!(result.is_err());
    let err = result.unwrap_err().to_string();
    assert!(
        err.contains("取消") || err.contains("执行失败"),
        "应返回取消相关错误,实际: {}",
        err
    );
    // 状态保持 Cancelled(未被 set_failed 覆盖为 Failed)—— R1 修法生效的核心验证
    assert_eq!(
        executor.state_machine.get(&"x".to_string()),
        NodeStatus::Cancelled
    );
}

/// 测试节点:执行中通过共享 node_status 自取消(模拟外部 IPC set_cancelled),返回 Ok
/// 模拟 HumanNode select! 收合法 HumanApprovalResponse 后 return Ok,但 join_all 让出窗口内
/// cancel_workflow_node IPC 把节点改 Cancelled 的 TOCTOU 场景。
struct CancelSelfThenOkNode;

#[async_trait]
impl Node for CancelSelfThenOkNode {
    async fn execute(&self, ctx: NodeContext) -> NodeResult {
        ctx.node_status.set_cancelled(ctx.node_id.clone());
        Ok(NodeOutput::empty())
    }

    fn schema(&self) -> NodeSchema {
        NodeSchema {
            params: serde_json::Value::Null,
            output: serde_json::Value::Null,
        }
    }

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

/// R-P1-3:Ok 后取消的 TOCTOU 防护。节点返回 Ok 但执行期间已被 set_cancelled,
/// executor Ok 分支必须跳过 set_completed(transition Cancelled→Completed 非法会 bail),
/// 状态保持 Cancelled,run 返回 Ok(已批准审批不应误报失败)。
#[tokio::test]
async fn test_cancelled_node_skips_set_completed() {
    use df_types::types::NodeStatus;
    let mut dag = Dag::new();
    dag.add_node("y".to_string(), Box::new(CancelSelfThenOkNode));

    let mut executor = DagExecutor::new(EventBus::new(), "test-cancel-ok".into());
    let result = executor.run(&dag, serde_json::Value::Null).await;

    // run 返回 Ok —— Ok 节点不应因 TOCTOU 取消被误判为失败
    assert!(
        result.is_ok(),
        "Ok 后取消应短路 set_completed 而非 bail,实际 err: {:?}",
        result.err()
    );
    // 状态保持 Cancelled(未被 set_completed 覆盖为 Completed,也不 bail)—— R-P1-3 修法核心验证
    assert_eq!(
        executor.state_machine.get(&"y".to_string()),
        NodeStatus::Cancelled
    );
}

/// 复核-新③:取消节点(Err 路径)emit 的应是 NodeCancelled(非 NodeFailed),
/// 前端按 type 区分「取消」与「失败」。订阅事件总线收集所有事件断言。
#[tokio::test]
async fn test_cancelled_node_emits_node_cancelled_event() {
    use df_types::events::WorkflowEvent;
    use df_types::types::NodeStatus;

    let mut dag = Dag::new();
    dag.add_node("c".to_string(), Box::new(CancelSelfNode));

    let bus = EventBus::new();
    let mut rx = bus.subscribe();
    let mut executor = DagExecutor::new(bus, "test-cancel-event".into());
    let _ = executor.run(&dag, serde_json::Value::Null).await;

    // 收集所有事件(run 已结束,事件总线无新事件)
    let mut events: Vec<WorkflowEvent> = Vec::new();
    while let Ok(ev) = rx.try_recv() {
        events.push(ev);
    }

    // 必含 NodeCancelled { node_id: "c" }
    let cancelled = events.iter().any(|e| matches!(
        e,
        WorkflowEvent::NodeCancelled { node_id } if node_id == "c"
    ));
    assert!(cancelled, "取消节点应 emit NodeCancelled, 实际事件: {:?}", events);

    // 不应含 node "c" 的 NodeFailed(失败事件)—— 语义双标修复的核心验证
    let failed = events.iter().any(|e| matches!(
        e,
        WorkflowEvent::NodeFailed { node_id, .. } if node_id == "c"
    ));
    assert!(!failed, "取消节点不应 emit NodeFailed, 实际事件: {:?}", events);

    // 状态保持 Cancelled
    assert_eq!(
        executor.state_machine.get(&"c".to_string()),
        NodeStatus::Cancelled
    );
}

// ============================================================================
// conditions-eval feature:边条件路由测试(feature flag 开启时才编译)
// ============================================================================

/// 测试节点:把 config.ok 作为 bool 输出到 { "ok": <bool> }。
/// 边 condition "$.ok == true" 据此判定路由。
#[cfg(feature = "conditions-eval")]
struct BoolFlagNode {
    ok: bool,
}

#[cfg(feature = "conditions-eval")]
#[async_trait]
impl Node for BoolFlagNode {
    async fn execute(&self, _ctx: NodeContext) -> NodeResult {
        Ok(NodeOutput::from_value(serde_json::json!({ "ok": self.ok })))
    }

    fn schema(&self) -> NodeSchema {
        NodeSchema {
            params: serde_json::Value::Null,
            output: serde_json::Value::Null,
        }
    }

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

/// 测试节点:执行时记录到共享 Mutex,用于断言节点是否被执行。
#[cfg(feature = "conditions-eval")]
struct RecordingNode {
    flag: std::sync::Arc<std::sync::Mutex<Vec<String>>>,
    name: String,
}

#[cfg(feature = "conditions-eval")]
#[async_trait]
impl Node for RecordingNode {
    async fn execute(&self, _ctx: NodeContext) -> NodeResult {
        self.flag.lock().unwrap().push(self.name.clone());
        Ok(NodeOutput::empty())
    }

    fn schema(&self) -> NodeSchema {
        NodeSchema {
            params: serde_json::Value::Null,
            output: serde_json::Value::Null,
        }
    }

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

/// conditions-eval 开启时:边 condition 求值 true → 下游执行。
///
/// 结构:branch(true) --condition "$.ok == true"--> target_run
///       branch(true) --condition "$.ok == false"--> target_skip
/// branch.ok==true:第一条边满足 → target_run 执行;第二条边 false → target_skip 跳过。
#[cfg(feature = "conditions-eval")]
#[tokio::test]
async fn conditions_eval_routes_by_edge_condition() {
    let mut dag = Dag::new();
    dag.add_node("branch".to_string(), Box::new(BoolFlagNode { ok: true }));

    let ran = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
    let ran_run = ran.clone();
    let ran_skip = ran.clone();

    dag.add_node(
        "target_run".to_string(),
        Box::new(RecordingNode { flag: ran_run, name: "target_run".to_string() }),
    );
    dag.add_node(
        "target_skip".to_string(),
        Box::new(RecordingNode { flag: ran_skip, name: "target_skip".to_string() }),
    );

    // 两条带条件的出边:branch.ok==true 命中第一条
    dag.add_edge_with_condition(
        "branch".to_string(),
        "target_run".to_string(),
        "$.ok == true".to_string(),
    );
    dag.add_edge_with_condition(
        "branch".to_string(),
        "target_skip".to_string(),
        "$.ok == false".to_string(),
    );

    let mut executor = DagExecutor::new(EventBus::new(), "test-cond-route".into());
    let outputs = executor
        .run(&dag, serde_json::Value::Null)
        .await
        .expect("run");

    // target_run 被执行(条件命中);target_skip 被跳过(条件不满足,不入 outputs)
    assert!(
        outputs.contains_key("target_run"),
        "条件命中的下游应执行,outputs: {:?}",
        outputs.keys().collect::<Vec<_>>()
    );
    assert!(
        !outputs.contains_key("target_skip"),
        "条件不满足的下游应跳过(不入 outputs),outputs: {:?}",
        outputs.keys().collect::<Vec<_>>()
    );

    let ran = ran.lock().unwrap();
    assert!(
        ran.contains(&"target_run".to_string()),
        "target_run 应被执行,ran: {:?}",
        ran
    );
    assert!(
        !ran.contains(&"target_skip".to_string()),
        "target_skip 不应被执行,ran: {:?}",
        ran
    );
}

/// conditions-eval 开启时:节点所有入边均带 condition 且全部 false → 跳过执行。
/// 验证状态机保持 Pending(跳过节点不 set_running/Completed/Failed)。
#[cfg(feature = "conditions-eval")]
#[tokio::test]
async fn conditions_eval_skip_keeps_pending() {
    use df_types::types::NodeStatus;

    let mut dag = Dag::new();
    dag.add_node("src".to_string(), Box::new(BoolFlagNode { ok: true }));
    dag.add_node(
        "skip_target".to_string(),
        Box::new(RecordingNode {
            flag: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
            name: "skip_target".to_string(),
        }),
    );
    // src.ok==true,但边要求 == false → 不满足 → 跳过
    dag.add_edge_with_condition(
        "src".to_string(),
        "skip_target".to_string(),
        "$.ok == false".to_string(),
    );

    let mut executor = DagExecutor::new(EventBus::new(), "test-cond-skip".into());
    let outputs = executor
        .run(&dag, serde_json::Value::Null)
        .await
        .expect("run");

    assert!(!outputs.contains_key("skip_target"), "skip_target 应被跳过不入 outputs");
    // 跳过的节点保持 Pending(未被状态机转换)
    assert_eq!(
        executor.state_machine.get(&"skip_target".to_string()),
        NodeStatus::Pending,
        "跳过的节点应保持 Pending"
    );
    assert_eq!(
        executor.state_machine.get(&"src".to_string()),
        NodeStatus::Completed,
        "源节点应正常完成"
    );
}

/// conditions-eval 开启时:无条件入边 + 条件入边混合,无条件的总放行(不被跳过)。
#[cfg(feature = "conditions-eval")]
#[tokio::test]
async fn conditions_eval_unconditional_edge_always_passes() {
    let mut dag = Dag::new();
    dag.add_node("a".to_string(), Box::new(BoolFlagNode { ok: true }));
    dag.add_node("b".to_string(), Box::new(BoolFlagNode { ok: false }));
    dag.add_node(
        "mix".to_string(),
        Box::new(RecordingNode {
            flag: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
            name: "mix".to_string(),
        }),
    );
    // 一条无条件边(b → mix)+ 一条条件边(a → mix, 要求 a.ok==false 不满足)
    dag.add_edge("b".to_string(), "mix".to_string());
    dag.add_edge_with_condition(
        "a".to_string(),
        "mix".to_string(),
        "$.ok == false".to_string(),
    );

    let mut executor = DagExecutor::new(EventBus::new(), "test-cond-mix".into());
    let outputs = executor
        .run(&dag, serde_json::Value::Null)
        .await
        .expect("run");

    // 存在无条件入边 → mix 必执行(无条件边不被跳过逻辑计入 has_any_cond_edge 的「全部 false」)
    assert!(
        outputs.contains_key("mix"),
        "存在无条件入边时节点应执行,outputs: {:?}",
        outputs.keys().collect::<Vec<_>>()
    );
}
