use super::util::{current_dir, display};
use koala_artifact::{verify, VerifyOptions, VerifyStatus};
use koala_core::invariant::{
    load_user_invariants, Context, Invariant, Outcome, Registry, UserDefinedInvariant,
};
use koala_drift::{Registry as DriftRegistry, Severity};
use koala_health::list_redlines;
use std::path::PathBuf;
use std::process::ExitCode;

#[allow(clippy::too_many_arguments)]
pub fn run(
    root: Option<PathBuf>,
    invariant_only: bool,
    drift_only: bool,
    no_artifact: bool,
    sample: u32,
    seed: Option<String>,
    profile: String,
) -> ExitCode {
    let root_path = root.unwrap_or_else(current_dir);
    let ctx = Context::new(root_path.clone());
    let strict = profile == "strict";

    let run_invariants = !drift_only;
    let run_drift = !invariant_only;
    let run_artifact = !invariant_only && !drift_only && !no_artifact;
    let run_redline = strict && !invariant_only && !drift_only;

    let total_steps = [run_invariants, run_drift, run_artifact, run_redline]
        .into_iter()
        .filter(|b| *b)
        .count();
    let mut step = 0usize;

    let mut invariant_fails = 0usize;
    let mut drift_hard = 0usize;
    let mut drift_advisory_blocking = 0usize;
    let mut artifact_fail = 0usize;
    let mut redline_blocks = 0usize;

    println!("profile: {profile}");

    if run_invariants {
        step += 1;
        let reg = Registry::builtin();
        let mut results: Vec<(String, Outcome)> = reg
            .evaluate_all(&ctx)
            .into_iter()
            .map(|(id, o)| (id.to_string(), o))
            .collect();

        // Append user-defined TOML rules. Failures inside the loader
        // (parse / unknown category) become a single synthetic Fail
        // outcome so the user sees the problem alongside builtin fails.
        let user_rules: Vec<UserDefinedInvariant> = match load_user_invariants(&root_path) {
            Ok(v) => v,
            Err(e) => {
                results.push((
                    "user-toml.loader".to_string(),
                    Outcome::fail(format!("failed to load user invariants: {e}")),
                ));
                Vec::new()
            }
        };
        let user_count = user_rules.len();
        for r in &user_rules {
            results.push((r.id().to_string(), r.evaluate(&ctx)));
        }

        let total = results.len();
        let mut fail_lines: Vec<String> = Vec::new();
        let mut skips = 0usize;
        for (id, outcome) in &results {
            match outcome {
                Outcome::Pass { .. } => {}
                Outcome::Fail { detail, reproduce } => {
                    invariant_fails += 1;
                    let mut buf = format!("    FAIL  {id}\n");
                    for line in detail.lines() {
                        buf.push_str(&format!("          {line}\n"));
                    }
                    if let Some(r) = reproduce {
                        buf.push_str(&format!("          reproduce: {r}\n"));
                    }
                    fail_lines.push(buf);
                }
                Outcome::Skip { .. } => skips += 1,
            }
        }
        let pass = total - invariant_fails - skips;
        let user_note = if user_count > 0 {
            format!(" [+{user_count} user-defined]")
        } else {
            String::new()
        };
        println!(
            "[{step}/{total_steps}] invariants ........ {pass} pass, {invariant_fails} fail, {skips} skip (of {total}){user_note}"
        );
        for line in &fail_lines {
            print!("{line}");
        }
    }

    if run_drift {
        step += 1;
        let dreg = DriftRegistry::builtin();
        let findings = dreg.run_all(&ctx);
        let mut adv = 0usize;
        for f in &findings {
            match f.severity {
                Severity::Hard => drift_hard += 1,
                Severity::Advisory => adv += 1,
            }
        }
        if strict {
            drift_advisory_blocking = adv;
        }
        let strict_note = if strict && adv > 0 {
            " (strict: advisories block)"
        } else {
            ""
        };
        println!(
            "[{step}/{total_steps}] drift ............. {drift_hard} hard, {adv} advisory{strict_note} (of {n} check(s))",
            n = dreg.len(),
        );
        for f in &findings {
            let blocking_in_this_profile =
                f.severity == Severity::Hard || (strict && f.severity == Severity::Advisory);
            if blocking_in_this_profile {
                println!(
                    "    {} {}:{} [{}] {}",
                    f.severity.label(),
                    f.file.display(),
                    f.line,
                    f.check_id,
                    f.kind.short(),
                );
            }
        }
    }

    if run_artifact {
        step += 1;
        let vopts = VerifyOptions {
            repo_root: root_path.clone(),
            sample_ratio_percent: sample,
            seed: seed.unwrap_or_else(|| "koala-artifact-default".into()),
            diff_lines: 5,
        };
        match verify(&vopts) {
            Ok(report) if report.total == 0 => {
                println!(
                    "[{step}/{total_steps}] artifact verify ... no artifacts under .review/round-*/"
                );
            }
            Ok(report) => {
                artifact_fail = report.mismatch_count() + report.error_count();
                println!(
                    "[{step}/{total_steps}] artifact verify ... {pass} match, {fail} tampered, {err} error ({sampled}/{total} sampled @ {sample}%)",
                    pass = report.pass_count(),
                    fail = report.mismatch_count(),
                    err = report.error_count(),
                    sampled = report.sampled,
                    total = report.total,
                );
                for o in &report.results {
                    match &o.status {
                        VerifyStatus::Match { .. } => {}
                        VerifyStatus::Mismatch {
                            expected,
                            actual,
                            diff,
                        } => {
                            println!("    ✗ TAMPERED  {}", display(&o.artifact));
                            println!("        expected: {expected}");
                            println!("        actual:   {actual}");
                            for line in diff {
                                println!("        {} {}", line.side.marker(), line.text);
                            }
                        }
                        VerifyStatus::Error(e) => {
                            println!("    ! error  {}  {e}", display(&o.artifact));
                        }
                    }
                }
            }
            Err(e) => {
                eprintln!("error: {e}");
                return ExitCode::from(2);
            }
        }
    }

    if run_redline {
        step += 1;
        let active = list_redlines(&root_path);
        redline_blocks = active.len();
        println!(
            "[{step}/{total_steps}] redlines .......... {n} active",
            n = redline_blocks,
        );
        for id in &active {
            println!("    ✗ .koala/redlines/{id}.md");
        }
    }

    let total_fail =
        invariant_fails + drift_hard + drift_advisory_blocking + artifact_fail + redline_blocks;
    println!();
    if total_fail == 0 {
        println!("✓ all gates passed");
        ExitCode::SUCCESS
    } else {
        println!("✗ {total_fail} finding(s) blocking");
        ExitCode::from(1)
    }
}
