//! 工作流相关命令 — 触发执行、查询执行记录
//!
//! 事件转发：run_workflow 在执行前订阅事件总线，
//! 将 WorkflowEvent 包装为 `{ execution_id, event }` 通过 `workflow-event` 事件发给前端。

use serde::Serialize;
use tauri::{AppHandle, Emitter, State};
use tokio::sync::broadcast::error::RecvError;

use df_types::events::{WorkflowEvent, HumanApprovalResponse, SelectType};
use df_types::types::{new_id, ExecutionId, NodeStatus};
use df_nodes::task_advance_node::advance_task_atomic;
// F-260616-06 ②-4: 工作流失败时按 target_status 推算任务退回态。
// regression_target 已收敛到 df-nodes::task_state_machine(状态机业务契约,单一可信源),
// 不再在 app crate 维护私有副本(防 DRY 漂移)。
use df_nodes::task_state_machine::regression_target;
// F-260616-06 ①-1: 空 dag + target_status 时按目标态自动选推进链模板
use df_nodes::task_workflow_templates::template_for;
use df_storage::crud::{TaskRepo, WorkflowRepo};
use df_storage::models::WorkflowRecord;
use df_workflow::dag_def::DagDef;
use df_workflow::executor::DagExecutor;

use crate::state::AppState;

use super::{err_str, now_millis};

/// 转发到前端的事件载荷
#[derive(Debug, Clone, Serialize)]
struct WorkflowEventPayload {
    /// 工作流执行记录 ID
    execution_id: ExecutionId,
    /// 原始工作流事件（serde tag = "type"）
    event: WorkflowEvent,
}

/// Lagged 兜底：查 DB 终态，命中返回 `(要补发的事件, status)`；未命中返回 None。
///
/// broadcast `Lagged` 不暴露被丢事件类型，关键终态事件（WorkflowCompleted/Failed）可能已丢，
/// forward 循环会永久等不到。达阈值时查 DB 终态补发。三种"未命中"（仍 running / 无记录 /
/// 查询失败）统一返回 None 并各自 warn，主循环据此重置 `lagged_total` 继续等。
async fn probe_lagged_terminal(
    db: &df_storage::db::Database,
    exec_id: &ExecutionId,
) -> Option<(WorkflowEvent, String)> {
    let record = match WorkflowRepo::new(db).get_by_id(exec_id).await {
        Ok(Some(r)) => r,
        Ok(None) => {
            tracing::warn!(
                execution_id = %exec_id,
                "Lagged 兜底查询未找到执行记录，继续等待事件"
            );
            return None;
        }
        Err(e) => {
            tracing::warn!(
                execution_id = %exec_id,
                error = %e,
                "Lagged 兜底查询 DB 失败，继续等待事件"
            );
            return None;
        }
    };
    let event = match record.status.as_str() {
        "completed" => WorkflowEvent::WorkflowCompleted {
            execution_id: exec_id.clone(),
            total_duration_ms: 0,
        },
        "failed" => WorkflowEvent::WorkflowFailed {
            execution_id: exec_id.clone(),
            error: "工作流执行失败（Lagged 兜底补发，详情见 DB）".to_string(),
            failed_node: String::new(),
        },
        "cancelled" => WorkflowEvent::WorkflowFailed {
            execution_id: exec_id.clone(),
            error: "工作流被取消（Lagged 兜底补发）".to_string(),
            failed_node: String::new(),
        },
        _ => return None,
    };
    Some((event, record.status))
}

/// 触发工作流执行（核心命令）
///
/// 流程：build_dag 校验 → 写入执行记录(status=running) → 后台异步执行 →
/// 事件经 EventBus 转发到前端 → 完成后更新执行记录状态。
/// 立即返回执行记录 ID，前端凭此关联后续事件。
///
/// F-260616-06 阶段2 工作流联动(②-2):
///   - `task_id` / `target_status` 同时 Some 时,完成后按 target_status 推进任务(②-3),
///     失败时按 regression_target 退回(②-4)。
///   - Option 向后兼容:旧调用方不传即 None,不触发任何任务回调,零行为破坏。
#[tauri::command]
pub async fn run_workflow(
    app: AppHandle,
    state: State<'_, AppState>,
    name: String,
    dag: DagDef,
    config: serde_json::Value,
    // F-260616-06 ②-2: 工作流联动任务的关联 ID 与目标态(同时 Some 才联动)
    task_id: Option<String>,
    target_status: Option<String>,
) -> Result<String, String> {
    // 瘦转发到 run_workflow_inner(B-260617-01:抽取共用核心供 ai_approve 经
    // execute_run_workflow_for_tool 直接调,无需构造 tauri::State)。
    // CR-52: 命令层(前端 invoke run_workflow)是人工手动触发 → triggered_by="manual"。
    run_workflow_inner(&app, &state, name, dag, config, task_id, target_status, "manual").await
}

/// 工作流执行核心逻辑(B-260617-01 抽取,命令层瘦转发)。
///
/// 原 `run_workflow` 命令体拆出,使 ai_approve 经 execute_run_workflow_for_tool 直接调用
/// (持 &AppState 而非 tauri::State,绕开 tauri::State 在非命令上下文不可构造的限制)。
/// 语义与原命令完全一致:模板选 DAG → 落执行记录 → spawn 事件转发 + DAG 执行 + 任务联动推进。
///
/// `triggered_by` 落入 WorkflowRecord.triggered_by,区分触发来源:
///   - 命令层 run_workflow(前端 invoke,人工手动)传 "manual"
///   - execute_run_workflow_for_tool(AI 工具经 ai_approve 审批后执行)传 "ai"
///   CR-52:原硬编码 "manual" 致 AI 路径误标,改由调用方透传。
pub async fn run_workflow_inner(
    app: &AppHandle,
    state: &AppState,
    name: String,
    dag: DagDef,
    config: serde_json::Value,
    task_id: Option<String>,
    target_status: Option<String>,
    triggered_by: &str,
) -> Result<String, String> {
    // 0. F-260616-06 ①-1: DagDef 来源选模板(方案A·最小)。
    //    规则:
    //    - dag.nodes 非空 → 用传入 dag(向后兼容,旧调用方/编辑器自定义工作流不变)
    //    - dag.nodes 为空 + target_status Some → 调 template_for 自动选推进链模板;
    //      模板不存在(target 非三合法态) → Err「无对应工作流模板」
    //    - dag.nodes 为空 + target_status None → 原行为(空 dag 让 build_dag 自行报错,零新分支)
    //    前端选 target_status 后传空 dag,零模板知识零新 IPC,后端收敛选模板逻辑。
    let dag = if dag.nodes.is_empty() {
        match target_status.as_deref() {
            Some(target) => template_for(target)
                .ok_or_else(|| format!("无对应工作流模板: target_status={}", target))?,
            None => dag, // 空dag+无target: 走原路径由 build_dag 校验
        }
    } else {
        dag
    };

    // 1. 先构建运行时 DAG，校验失败直接返回，不落库
    let runtime_dag = state.registry.build_dag(&dag).map_err(err_str)?;

    // 2. 写入执行记录（status=running）
    let execution_id: ExecutionId = new_id().into();
    let dag_json = serde_json::to_string(&dag).map_err(err_str)?;
    let record = WorkflowRecord {
        id: execution_id.to_string(),
        name,
        dag_json,
        status: "running".to_string(),
        // CR-52: triggered_by 由调用方透传(命令层 run_workflow="manual",
        // execute_run_workflow_for_tool="ai"),不再硬编码 "manual" 致 AI 路径误标。
        triggered_by: Some(triggered_by.to_string()),
        project_id: None,
        task_id: task_id.clone(),
        created_at: now_millis(),
        completed_at: None,
    };
    state
        .workflows
        .insert(record)
        .await
        .map_err(err_str)?;

    // 3. 订阅事件总线，把事件转发给前端（收到完成/失败事件后退出）
    let mut rx = state.event_bus.subscribe();
    let forward_app = app.clone();
    let forward_exec_id = execution_id.clone();
    // B-260615-35: forward 任务内需查 DB 终态兜底 Lagged 丢终态事件，故 move 进 db 句柄
    let forward_db = state.db.clone();
    tauri::async_runtime::spawn(async move {
        // Lagged 累计次数（用于"累计达阈值"触发兜底查询）
        let mut lagged_total: u64 = 0;
        // Lagged 单次/累计触发 DB 终态查询兜底的阈值
        const LAGGED_PROBE_THRESHOLD: u64 = 8;
        loop {
            match rx.recv().await {
                Ok(event) => {
                    // B-03b-R10 ③(波17 治本): 完成判定加 execution_id 匹配。
                    // AppState.event_bus 是全局单例,并发工作流各自订阅同一总线;
                    // 此前 matches! 只看变体不看 exec_id,任一工作流的 WorkflowCompleted/Failed
                    // 都会触发本 forward 循环 break,过早断他人链(并发串扰根因)。
                    // 现在按变体+exec_id 双重匹配:只处理属于本 forward_exec_id 的终态事件,
                    // 他人终态事件忽略(emit 给前端后继续循环),不再误 break。
                    // execution_id 字段 #[serde(default)] 向后兼容老事件(空串):
                    // 自家终态事件本就带正确 exec_id;空串匹配只可能漏(自家老事件),不会误 break。
                    let finished = matches!(
                        &event,
                        WorkflowEvent::WorkflowCompleted { execution_id, .. }
                            | WorkflowEvent::WorkflowFailed { execution_id, .. }
                            // execution_id: WorkflowEvent 中反序列化的 ExecutionId(#[serde(default)]),
                            // forward_exec_id: 本 forward 循环所属的 ExecutionId,同类型直接比较
                            if execution_id == &forward_exec_id
                    );
                    let payload = WorkflowEventPayload {
                        execution_id: forward_exec_id.clone(),
                        event,
                    };
                        if let Err(e) = forward_app.emit("workflow-event", &payload) {
                        tracing::warn!("工作流事件转发失败: {}", e);
                    }
                    if finished {
                        break;
                    }
                }
                // R-PD-13: Lagged 表示消费过慢，broadcast 滑动窗口丢 n 条最旧事件。
                // broadcast 不暴露被丢事件的具体类型，无法在此精确判断是否为关键终态事件。
                // 风险：若关键终态事件（WorkflowCompleted/WorkflowFailed）被丢，finished 判定不触发，
                //      循环不会 break，前端永久收不到工作流结束信号。
                // B-260615-35 兜底：收到 Lagged 时，累计次数或单次 n 达阈值则查 DB 终态，
                //                   若工作流已终态则补发对应 workflow-event 并 break 退出 forward。
                //                   避免依赖实时性差的 DB 轮询兜底导致前端审批/完成弹窗卡死。
                Err(RecvError::Lagged(n)) => {
                    lagged_total = lagged_total.saturating_add(n);
                    tracing::warn!(
                        execution_id = %forward_exec_id,
                        lagged = n,
                        lagged_total = lagged_total,
                        "工作流事件消费滞后，丢失 {} 条事件（broadcast 不暴露被丢事件类型，关键终态事件可能已丢失）",
                        n
                    );
                    if lagged_total < LAGGED_PROBE_THRESHOLD {
                        continue;
                    }
                    // 累计/单次达阈值：查 DB 终态兜底（probe_lagged_terminal 收口三种未命中情况）
                    match probe_lagged_terminal(&forward_db, &forward_exec_id).await {
                        Some((synth_event, status)) => {
                            let payload = WorkflowEventPayload {
                                execution_id: forward_exec_id.clone(),
                                event: synth_event.clone(),
                            };
                            if let Err(e) = forward_app.emit("workflow-event", &payload) {
                                tracing::warn!("Lagged 终态兜底事件转发失败: {}", e);
                            }
                            tracing::info!(
                                execution_id = %forward_exec_id,
                                status = %status,
                                "Lagged 兜底命中终态，补发 {} 并退出 forward",
                                match synth_event {
                                    WorkflowEvent::WorkflowCompleted { .. } => "WorkflowCompleted",
                                    _ => "WorkflowFailed",
                                }
                            );
                            break;
                        }
                        None => {
                            // 未命中(仍 running / 无记录 / 查询失败):重置累计计数继续等待
                            lagged_total = 0;
                        }
                    }
                }
                Err(RecvError::Closed) => break,
            }
        }
    });

    // 4. 后台异步执行 DAG，完成后更新执行记录状态
    let event_bus = state.event_bus.clone();
    let db = state.db.clone();
    let exec_id = execution_id.clone();
    let state_registry = state.workflow_state_registry.clone();
    // F-260616-06 ②-2: move task_id / target_status 进闭包供完成/失败回调使用
    let cb_task_id = task_id.clone();
    let cb_target_status = target_status.clone();
    // panic-guard:执行 spawn 闭包最外层包 catch_unwind(AssertUnwindSafe(..))。
    // panic 时走已存在 failed 分支(update status=failed + emit WorkflowFailed + state_registry.remove),
    // 防 panic 后终态不发/registry 不清永久卡。主逻辑/调度零改动,仅在 panic 时补发终态。
    // 注:依赖 panic=unwind(tokio 默认),若构建设 panic=abort 则 catch_unwind 不生效(尽力兜底)。
    tauri::async_runtime::spawn({
        // panic 兜底需要这些引用:move 进 catch_unwind 闭包前各取一份 clone 供 panic 分支使用。
        let panic_exec_id = exec_id.clone();
        let panic_db = db.clone();
        let panic_event_bus = event_bus.clone();
        let panic_registry = state_registry.clone();
        async move {
            use std::panic::AssertUnwindSafe;
            use futures::FutureExt;
            // 原闭包主体包进 catch_unwind:panic 以 Err(Box<dyn Any>) 返回,Ok 走正常路径。
            let outcome = AssertUnwindSafe(async move {
        let mut executor = DagExecutor::new(event_bus.clone(), exec_id.clone());
        // 注册执行器状态机：StateMachine 内部 Arc<Mutex>，clone 共享底层 HashMap，
        // cancel_workflow_node IPC 经 execution_id 取此引用 set_cancelled，直达运行中 HumanNode
        state_registry
            .lock()
            .await
            .insert(exec_id.clone(), executor.state_machine());
        let result = executor.run(&runtime_dag, config).await;

        let (status, error) = match &result {
            Ok(_) => ("completed", None),
            Err(e) => ("failed", Some(format!("{:#}", e))),
        };

        // 更新执行记录（Repo 内部仅持有 Arc 连接，重建开销可忽略）
        let workflows = WorkflowRepo::new(&db);
        if let Err(e) = workflows.update_field(&exec_id, "status", status).await {
            tracing::error!("更新工作流状态失败: {}", e);
        }
        if let Err(e) = workflows
            .update_field(&exec_id, "completed_at", &now_millis())
            .await
        {
            tracing::error!("更新工作流完成时间失败: {}", e);
        }

        // F-260616-06 ②-3 / ②-4: 工作流联动任务推进回调
        //   - 成功(completed): task_id 与 target_status 都 Some 时推进到 target_status
        //   - 失败(failed):    按 regression_target 推算退回态推进;无映射则跳过
        //   task_id / target_status 任一 None → 跳过(向后兼容,旧工作流不联动任务)
        //   回调失败仅 tracing::warn!,不回滚(工作流成功语义与任务推进解耦 — 回调失败不撤销
        //   已完成工作流,前端可后续手动处理)
        match (cb_task_id.as_ref(), cb_target_status.as_deref()) {
            (Some(tid), Some(target)) => {
                let advance_target = match status {
                    "completed" => Some(target),
                    "failed" => regression_target(target),
                    _ => None,
                };
                if let Some(to) = advance_target {
                    let repo = TaskRepo::new(&db);
                    if let Err(e) = advance_task_atomic(&repo, tid, to).await {
                        tracing::warn!(
                            execution_id = %exec_id,
                            task_id = %tid,
                            target_status = %to,
                            workflow_status = %status,
                            "工作流联动推进任务失败(不回滚): {}",
                            e
                        );
                    } else {
                        tracing::info!(
                            execution_id = %exec_id,
                            task_id = %tid,
                            target_status = %to,
                            workflow_status = %status,
                            "工作流联动推进任务成功"
                        );
                    }
                } else {
                    // failed 且无退回映射:warn 跳过(常见:target 是 todo 起点无可退态)
                    tracing::warn!(
                        execution_id = %exec_id,
                        task_id = %tid,
                        target_status = %target,
                        "工作流失败但 target_status 无退回映射,跳过任务回调"
                    );
                }
            }
            _ => {}
        }

        // 执行结束（成功/失败）清理状态注册表，防内存泄漏
        state_registry.lock().await.remove(&exec_id);

        // 执行失败时补发 WorkflowFailed（执行器内部只发 NodeFailed）
        // failed_node 从状态机取首个失败/取消节点(R9⑪: 原 String::new() 空值)
        if let Some(error) = error {
            let failed_node = executor
                .state_machine()
                .snapshot()
                .into_iter()
                .find(|(_, s)| matches!(s, NodeStatus::Failed | NodeStatus::Cancelled))
                .map(|(id, _)| id)
                .unwrap_or_default();
            event_bus
                .send(WorkflowEvent::WorkflowFailed {
                    execution_id: exec_id.clone(),
                    error,
                    failed_node,
                })
                .await;
        }
            }) // 闭 catch_unwind 内 async move
            .catch_unwind()
            .await;

            // panic 分支:catch_unwind 返回 Err(Box<dyn Any + Send>),走已存在 failed 清理路径。
            match outcome {
                Ok(_) => {}
                Err(panic_payload) => {
                    // 提取 panic 消息(String/&'static str 常见,其他类型用兜底文案)
                    let msg = panic_payload
                        .downcast_ref::<String>()
                        .map(|s| s.clone())
                        .or_else(|| panic_payload.downcast_ref::<&'static str>().map(|s| s.to_string()))
                        .unwrap_or_else(|| "工作流执行 panic".to_string());
                    tracing::error!(
                        execution_id = %panic_exec_id,
                        "工作流执行 panic,走 failed 分支兜底: {}",
                        msg
                    );
                    let workflows = WorkflowRepo::new(&panic_db);
                    if let Err(e) = workflows.update_field(&panic_exec_id, "status", "failed").await {
                        tracing::error!("更新工作流状态失败(panic 兜底): {}", e);
                    }
                    if let Err(e) = workflows
                        .update_field(&panic_exec_id, "completed_at", &now_millis())
                        .await
                    {
                        tracing::error!("更新工作流完成时间失败(panic 兜底): {}", e);
                    }
                    panic_registry.lock().await.remove(&panic_exec_id);
                    panic_event_bus
                        .send(WorkflowEvent::WorkflowFailed {
                            execution_id: panic_exec_id.clone(),
                            error: msg,
                            failed_node: String::new(),
                        })
                        .await;
                }
            }
        }
    });

    Ok(execution_id.to_string())
}

/// 列出工作流执行记录(最近 N 条,对标设计 §10.5 单用户桌面应用分页)。
///
/// M19:原实现调 `list_all()` 无 limit/分页,工作流执行记录随使用累积(workflow_executions 表
/// 只插不删,每次 run_workflow 落一条),长期使用后全量返回会撑爆前端列表 + IPC 传输 + 内存。
/// 加可选 `limit` 参数(默认 100,钳制 ≤ 500),list_all 已按 created_at DESC 排序,截断取最近 N 条。
/// 前端旧调用方不传 limit 走默认 100,零破坏(工作流历史本就按时间倒序展示,截断尾部老记录无感)。
#[tauri::command]
pub async fn list_workflow_executions(
    state: State<'_, AppState>,
    limit: Option<u32>,
) -> Result<Vec<WorkflowRecord>, String> {
    let safe_limit = limit.unwrap_or(100).min(500) as usize;
    let mut records = state.workflows.list_all().await.map_err(err_str)?;
    if records.len() > safe_limit {
        records.truncate(safe_limit);
    }
    Ok(records)
}

/// 按 ID 查询工作流执行记录
#[tauri::command]
pub async fn get_workflow_execution(
    state: State<'_, AppState>,
    id: String,
) -> Result<Option<WorkflowRecord>, String> {
    state
        .workflows
        .get_by_id(&id)
        .await
        .map_err(err_str)
}

/// 发送人工审批响应
///
/// F-260615-01: 支持 single/multiple。
///   - single: decision 单值,decisions 为空(向后兼容);
///   - multiple: decisions 数组(≥1 项),decision 留空。
/// IPC 早校验规则与 HumanNode 下游兜底一致,避免「IPC 成功+工作流失败」割裂。
#[tauri::command]
pub async fn approve_human_approval(
    app: AppHandle,
    state: State<'_, AppState>,
    execution_id: ExecutionId,
    node_id: String,
    decision: String,
    // F-260615-01: 多选结果数组,缺省空数组(单选调用方不传)
    decisions: Option<Vec<String>>,
    comment: Option<String>,
    // R-PD-5: options 由前端从收到的 HumanApprovalRequest 事件透传（IPC 无法访问节点 config）。
    options: Vec<String>,
    // F-260615-01: 选择类型,缺省 single
    select_type: Option<String>,
) -> Result<(), String> {
    let _ = &app; // 原签名含 app 参数(未使用),保留避免 invoke_handler 注册签名变化
    let select_type = match select_type.as_deref() {
        Some("multiple") => SelectType::Multiple,
        _ => SelectType::Single,
    };
    let decisions = decisions.unwrap_or_default();

    // 归一化决策集合(与 HumanNode 下游一致)
    let mut picked: Vec<String> = decisions;
    if picked.is_empty() && !decision.is_empty() {
        picked.push(decision.clone());
    }

    // 数量校验
    match select_type {
        SelectType::Single if picked.len() != 1 => {
            return Err("单选审批只能提交一个决策".to_string());
        }
        SelectType::Multiple if picked.is_empty() => {
            return Err("多选审批至少提交一个决策".to_string());
        }
        _ => {}
    }

    // 每项非空 + ∈options(options 非空时)
    for d in &picked {
        if d.trim().is_empty() {
            return Err("审批决策不能为空".to_string());
        }
        if !options.is_empty() && !options.contains(d) {
            return Err(format!("审批决策非法: {}", d));
        }
    }

    // 发送审批响应到事件总线(decision 取首项保留,decisions 透传全量)
    let primary = picked.first().cloned().unwrap_or_default();
    let response = HumanApprovalResponse {
        execution_id: execution_id.clone(),
        node_id: node_id.clone(),
        decision: primary,
        decisions: picked,
        comment,
    };

    let event = WorkflowEvent::HumanApprovalResponse {
        execution_id: response.execution_id,
        node_id: response.node_id,
        decision: response.decision,
        decisions: response.decisions,
        comment: response.comment,
    };

    // 直接发送到全局事件总线
    state.event_bus.send(event).await;
    Ok(())
}

/// 取消工作流节点（人工审批取消）
///
/// 从执行器状态机注册表取出共享引用，调 `set_cancelled` 置目标节点为 Cancelled。
/// StateMachine 内部 Arc<Mutex> 共享底层 HashMap，IPC 写入直达运行中阻塞节点（HumanNode）
/// 的 select! 轮询分支，其 is_cancelled 检测到后返回 Err "人工审批被取消"。
///
/// 终态前置守卫：仅 Pending（排队中）/ Running（执行中，含阻塞等审批）允许取消；
/// 终态节点（Completed/Failed/Skipped/Cancelled）返 Err，避免静默覆盖终态。
/// 守卫放 IPC 层（非 set_cancelled 内）：保留 set_cancelled 作为 executor 内部受控旁路语义，
/// 让状态变更的合法入口收敛到 IPC 这一道。
#[tauri::command]
pub async fn cancel_workflow_node(
    state: State<'_, AppState>,
    execution_id: ExecutionId,
    node_id: String,
) -> Result<(), String> {
    // 经 execution_id 取执行器状态机（与 NodeContext.node_status 共享同一 HashMap）
    let registry = state.workflow_state_registry.lock().await;
    let sm = match registry.get(&execution_id) {
        Some(sm) => sm,
        None => return Err(format!("工作流 {} 不存在或已结束", execution_id)),
    };
    // 终态守卫：get() 返回缺失条目默认 Pending（尚未执行）→ 允许取消
    let current = sm.get(&node_id);
    match current {
        NodeStatus::Pending | NodeStatus::Running | NodeStatus::Waiting => {
            sm.set_cancelled(node_id.clone());
            tracing::info!(
                "工作流节点取消：execution_id={}, node_id={}",
                execution_id,
                node_id
            );
            Ok(())
        }
        // 终态：不允许取消（避免静默覆盖终态）
        NodeStatus::Completed | NodeStatus::Failed | NodeStatus::Skipped | NodeStatus::Cancelled => {
            Err(format!(
                "节点 {} 已终态({})，无法取消",
                node_id,
                current.as_str()
            ))
        }
    }
}
