//! YAML 模板加载器 — 从 YAML 字符串加载 DagDef + 校验
//!
//! 模板格式:
//! ```yaml
//! name: 代码审查
//! description: AI 驱动的代码审查流程
//! nodes:
//!   review:
//!     type: ai
//!     config:
//!       prompt: "审查以下代码变更..."
//!       persona_id: reviewer
//!   notify:
//!     type: notify
//!     config:
//!       type: desktop
//!       title: "审查完成"
//! edges:
//!   - from: review
//!     to: notify
//! ```

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::dag_def::{DagDef, EdgeDef, NodeDef};

/// YAML 模板顶层结构
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkflowTemplate {
    pub name: String,
    #[serde(default)]
    pub description: String,
    pub nodes: HashMap<String, TemplateNode>,
    #[serde(default)]
    pub edges: Vec<TemplateEdge>,
}

/// 模板节点(YAML 友好命名)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateNode {
    /// 节点类型(ai/script/human/git/http/notify/subflow)
    #[serde(rename = "type")]
    pub node_type: String,
    #[serde(default)]
    pub config: serde_json::Value,
    #[serde(default)]
    pub label: Option<String>,
}

/// 模板边(YAML 友好命名)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemplateEdge {
    pub from: String,
    pub to: String,
    #[serde(default)]
    pub condition: Option<String>,
}

/// 模板加载错误
#[derive(Debug)]
pub enum TemplateError {
    /// YAML 解析失败
    YamlParse(String),
    /// 校验失败(空节点/未知类型/依赖环)
    Validation(String),
}

impl std::fmt::Display for TemplateError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::YamlParse(msg) => write!(f, "YAML 解析失败: {}", msg),
            Self::Validation(msg) => write!(f, "模板校验失败: {}", msg),
        }
    }
}

impl std::error::Error for TemplateError {}

/// 已知的节点类型白名单
const KNOWN_NODE_TYPES: &[&str] = &[
    "ai", "script", "human", "git", "http", "notify", "subflow",
];

/// 从 YAML 字符串加载模板并转换为 DagDef
///
/// 步骤:
/// 1. YAML → WorkflowTemplate(serde_yaml)
/// 2. 校验(节点非空/类型合法/边引用存在/无环)
/// 3. 转换为 DagDef
pub fn load_template(yaml: &str) -> Result<DagDef, TemplateError> {
    // 1. YAML 解析
    let template: WorkflowTemplate = serde_yaml::from_str(yaml)
        .map_err(|e| TemplateError::YamlParse(e.to_string()))?;

    // 2. 校验
    validate_template(&template)?;

    // 3. 转换为 DagDef
    let mut dag = DagDef::new();
    for (id, node) in &template.nodes {
        dag.nodes.insert(
            id.clone(),
            NodeDef {
                id: id.clone(),
                node_type: node.node_type.clone(),
                config: node.config.clone(),
                label: node.label.clone(),
            },
        );
    }
    for edge in &template.edges {
        dag.edges.push(EdgeDef {
            source: edge.from.clone(),
            target: edge.to.clone(),
            condition: edge.condition.clone(),
        });
    }

    Ok(dag)
}

/// 校验模板结构
fn validate_template(t: &WorkflowTemplate) -> Result<(), TemplateError> {
    // 节点非空
    if t.nodes.is_empty() {
        return Err(TemplateError::Validation("模板节点不能为空".into()));
    }

    // 节点类型合法
    for (id, node) in &t.nodes {
        if !KNOWN_NODE_TYPES.contains(&node.node_type.as_str()) {
            return Err(TemplateError::Validation(format!(
                "节点 '{}' 使用了未知类型 '{}'，已知类型: {:?}",
                id, node.node_type, KNOWN_NODE_TYPES
            )));
        }
    }

    // 边引用的节点存在
    let node_ids: std::collections::HashSet<&String> = t.nodes.keys().collect();
    for edge in &t.edges {
        if !node_ids.contains(&edge.from) {
            return Err(TemplateError::Validation(format!(
                "边的起点 '{}' 不存在", edge.from
            )));
        }
        if !node_ids.contains(&edge.to) {
            return Err(TemplateError::Validation(format!(
                "边的终点 '{}' 不存在", edge.to
            )));
        }
    }

    // 依赖无环(DFS 检测)
    if has_cycle(&t.edges) {
        return Err(TemplateError::Validation("依赖存在环".into()));
    }

    Ok(())
}

/// DFS 检测边列表是否有环
fn has_cycle(edges: &[TemplateEdge]) -> bool {
    use std::collections::{HashMap, HashSet};

    // 建邻接表
    let mut adj: HashMap<&str, Vec<&str>> = HashMap::new();
    for e in edges {
        adj.entry(e.from.as_str()).or_default().push(e.to.as_str());
    }

    let mut visited: HashSet<&str> = HashSet::new();
    let mut in_stack: HashSet<&str> = HashSet::new();

    fn dfs<'a>(
        node: &'a str,
        adj: &HashMap<&'a str, Vec<&'a str>>,
        visited: &mut HashSet<&'a str>,
        in_stack: &mut HashSet<&'a str>,
    ) -> bool {
        if in_stack.contains(node) {
            return true; // 环
        }
        if visited.contains(node) {
            return false; // 已访问过,无环
        }
        visited.insert(node);
        in_stack.insert(node);
        if let Some(neighbors) = adj.get(node) {
            for next in neighbors {
                if dfs(next, adj, visited, in_stack) {
                    return true;
                }
            }
        }
        in_stack.remove(node);
        false
    }

    for e in edges {
        let start = e.from.as_str();
        if dfs(start, &adj, &mut visited, &mut in_stack) {
            return true;
        }
    }
    false
}

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

    #[test]
    fn tpl_01_valid_template_loads() {
        let yaml = r#"
name: 代码审查
description: AI 驱动的代码审查
nodes:
  review:
    type: ai
    config:
      prompt: "审查代码"
  notify:
    type: notify
    config:
      type: desktop
      title: "完成"
edges:
  - from: review
    to: notify
"#;
        let dag = load_template(yaml).unwrap();
        assert_eq!(dag.nodes.len(), 2);
        assert_eq!(dag.edges.len(), 1);
        assert!(dag.nodes.contains_key("review"));
        assert_eq!(dag.nodes.get("review").unwrap().node_type, "ai");
    }

    #[test]
    fn tpl_02_empty_nodes_rejected() {
        let yaml = "name: 空\nnodes: {}\n";
        let result = load_template(yaml);
        assert!(matches!(result, Err(TemplateError::Validation(_))));
    }

    #[test]
    fn tpl_03_unknown_node_type_rejected() {
        let yaml = r#"
name: test
nodes:
  bad:
    type: unknown_type
    config: {}
"#;
        let result = load_template(yaml);
        assert!(matches!(result, Err(TemplateError::Validation(_))));
    }

    #[test]
    fn tpl_04_cycle_detected() {
        let yaml = r#"
name: 环
nodes:
  a:
    type: ai
    config: {}
  b:
    type: ai
    config: {}
edges:
  - from: a
    to: b
  - from: b
    to: a
"#;
        let result = load_template(yaml);
        assert!(matches!(result, Err(TemplateError::Validation(ref msg)) if msg.contains("环")));
    }

    #[test]
    fn tpl_05_edge_to_nonexistent_rejected() {
        let yaml = r#"
name: test
nodes:
  a:
    type: ai
    config: {}
edges:
  - from: a
    to: ghost
"#;
        let result = load_template(yaml);
        assert!(matches!(result, Err(TemplateError::Validation(_))));
    }

    #[test]
    fn tpl_06_no_edges_ok() {
        let yaml = r#"
name: 单节点
nodes:
  solo:
    type: script
    config:
      command: "echo hello"
"#;
        let dag = load_template(yaml).unwrap();
        assert_eq!(dag.nodes.len(), 1);
        assert!(dag.edges.is_empty());
    }

    #[test]
    fn tpl_07_invalid_yaml_rejected() {
        let yaml = "not: valid: yaml: {{{";
        let result = load_template(yaml);
        assert!(matches!(result, Err(TemplateError::YamlParse(_))));
    }

    #[test]
    fn tpl_08_all_known_node_types() {
        for nt in KNOWN_NODE_TYPES {
            let yaml = format!(
                "name: test\nnodes:\n  n:\n    type: {}\n    config: {{}}",
                nt
            );
            let result = load_template(&yaml);
            assert!(result.is_ok(), "节点类型 {} 应合法", nt);
        }
    }
}
