use std::collections::{HashMap, HashSet, VecDeque};

use crate::{error::ReflectionError, mode::ActionStep};

/// Full DAG validation: uniqueness + referential integrity + cycle detection.
pub fn validate(steps: &[ActionStep]) -> Result<(), ReflectionError> {
    check_uniqueness(steps)?;
    check_referential_integrity(steps)?;
    detect_cycles(steps)?;
    Ok(())
}

/// Kahn's algorithm: returns steps grouped into parallel execution layers.
/// Layer 0 has no dependencies; layer N depends only on layers < N.
/// Returns `Err(CycleDetected)` if the graph is not a DAG.
pub fn topological_layers(steps: &[ActionStep]) -> Result<Vec<Vec<String>>, ReflectionError> {
    // in_degree[id] = number of steps id depends on
    let mut in_degree: HashMap<&str, usize> = steps
        .iter()
        .map(|s| (s.id.as_str(), s.depends_on.len()))
        .collect();

    // dependents[id] = steps that depend on id (reverse edges)
    let mut dependents: HashMap<&str, Vec<&str>> =
        steps.iter().map(|s| (s.id.as_str(), vec![])).collect();

    for step in steps {
        for dep in &step.depends_on {
            dependents
                .entry(dep.step.as_str())
                .or_default()
                .push(step.id.as_str());
        }
    }

    let mut queue: VecDeque<&str> = in_degree
        .iter()
        .filter_map(|(&id, &d)| (d == 0).then_some(id))
        .collect();

    let mut layers: Vec<Vec<String>> = Vec::new();
    let mut visited = 0usize;

    while !queue.is_empty() {
        let layer: Vec<&str> = queue.drain(..).collect();
        visited += layer.len();

        let mut next: Vec<&str> = Vec::new();
        for &node in &layer {
            for &dep in dependents.get(node).map(Vec::as_slice).unwrap_or(&[]) {
                let d = in_degree
                    .get_mut(dep)
                    .expect("all step ids must be in in_degree — check uniqueness first");
                *d -= 1;
                if *d == 0 {
                    next.push(dep);
                }
            }
        }

        layers.push(layer.iter().map(|&s| s.to_string()).collect());
        queue.extend(next);
    }

    if visited != steps.len() {
        Err(ReflectionError::CycleDetected)
    } else {
        Ok(layers)
    }
}

fn check_uniqueness(steps: &[ActionStep]) -> Result<(), ReflectionError> {
    let mut seen: HashSet<&str> = HashSet::with_capacity(steps.len());
    for step in steps {
        if !seen.insert(step.id.as_str()) {
            return Err(ReflectionError::DuplicateStepId(step.id.clone()));
        }
    }
    Ok(())
}

fn check_referential_integrity(steps: &[ActionStep]) -> Result<(), ReflectionError> {
    let ids: HashSet<&str> = steps.iter().map(|s| s.id.as_str()).collect();
    let bad: Vec<String> = steps
        .iter()
        .flat_map(|step| {
            step.depends_on.iter().filter_map(|dep| {
                if ids.contains(dep.step.as_str()) {
                    None
                } else {
                    Some(format!(
                        "step '{}' depends_on unknown '{}'",
                        step.id, dep.step
                    ))
                }
            })
        })
        .collect();

    if bad.is_empty() {
        Ok(())
    } else {
        Err(ReflectionError::BadDependencyRefs(bad))
    }
}

fn detect_cycles(steps: &[ActionStep]) -> Result<(), ReflectionError> {
    topological_layers(steps).map(|_| ())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::mode::{Dependency, DependencyKind, OnError};

    fn step(id: &str, deps: &[&str]) -> ActionStep {
        ActionStep {
            id: id.to_string(),
            action: id.to_string(),
            actor: crate::mode::Actor::Both,
            cmd: None,
            depends_on: deps
                .iter()
                .map(|d| Dependency {
                    step: d.to_string(),
                    kind: DependencyKind::Always,
                    condition: None,
                })
                .collect(),
            on_error: OnError::default(),
            verify: None,
            note: None,
        }
    }

    #[test]
    fn linear_chain_produces_single_layers() {
        let steps = vec![step("a", &[]), step("b", &["a"]), step("c", &["b"])];
        let layers = topological_layers(&steps).unwrap();
        assert_eq!(layers.len(), 3);
        assert_eq!(layers[0], vec!["a"]);
        assert_eq!(layers[1], vec!["b"]);
        assert_eq!(layers[2], vec!["c"]);
    }

    #[test]
    fn parallel_deps_form_single_layer() {
        // a → {b, c} → d
        let steps = vec![
            step("a", &[]),
            step("b", &["a"]),
            step("c", &["a"]),
            step("d", &["b", "c"]),
        ];
        let layers = topological_layers(&steps).unwrap();
        assert_eq!(layers.len(), 3);
        assert_eq!(layers[0], vec!["a"]);
        assert!(layers[1].contains(&"b".to_string()));
        assert!(layers[1].contains(&"c".to_string()));
        assert_eq!(layers[2], vec!["d"]);
    }

    #[test]
    fn cycle_detected() {
        let steps = vec![step("a", &["b"]), step("b", &["a"])];
        assert!(matches!(
            topological_layers(&steps),
            Err(ReflectionError::CycleDetected)
        ));
    }

    #[test]
    fn duplicate_id_rejected() {
        let steps = vec![step("a", &[]), step("a", &[])];
        assert!(matches!(
            check_uniqueness(&steps),
            Err(ReflectionError::DuplicateStepId(_))
        ));
    }

    #[test]
    fn bad_ref_rejected() {
        let steps = vec![step("a", &["nonexistent"])];
        assert!(matches!(
            check_referential_integrity(&steps),
            Err(ReflectionError::BadDependencyRefs(_))
        ));
    }

    #[test]
    fn validate_passes_for_valid_dag() {
        let steps = vec![
            step("init_repo", &[]),
            step("copy_ontology", &["init_repo"]),
            step("init_kogral", &["init_repo"]),
            step("publish", &["copy_ontology", "init_kogral"]),
        ];
        assert!(validate(&steps).is_ok());
    }
}
