use super::util::current_dir;
use koala_core::invariant::Registry;
use koala_drift::Registry as DriftRegistry;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

const TEMPLATE_WORKFLOW_URL: &str = "https://github.com/koala-agent/koala-core/blob/main/crates/koala-cli/templates/.github/workflows/koala.yml"; // koala:allow=security.no-hardcoded-url reason=docs-pointer-shown-when-ci-not-wired

pub fn run(root: Option<PathBuf>) -> ExitCode {
    let root = root.unwrap_or_else(current_dir);

    println!("koala-core (v{})", env!("CARGO_PKG_VERSION"));
    println!();
    println!("  Crates:        {}", count_workspace_crates(&root));
    println!("  Invariants:    {} built-in", Registry::builtin().len());
    println!(
        "  Drift checks:  {} built-in",
        DriftRegistry::builtin().len()
    );
    println!("  Artifacts:     {}", count_artifacts(&root));
    println!();
    if let Some(line) = git_head_oneline(&root) {
        println!("  HEAD:          {line}");
    }
    if let Some(branch) = git_current_branch(&root) {
        println!("  Branch:        {branch}");
    }

    if let Some(msg) = ci_gate_warning(&root) {
        println!();
        println!("{msg}");
    }
    ExitCode::SUCCESS
}

#[derive(Debug, PartialEq, Eq)]
enum CiGateStatus {
    /// `.github/workflows/` does not exist — likely a brand-new repo
    /// or a host that isn't GitHub. Stay silent; not a problem `status`
    /// can diagnose.
    NoWorkflowsDir,
    /// Workflows exist but none invoke `koala-core check`. Either the
    /// project never wired the gate up, or it was deleted ("bootstrap
    /// 期豁免" that was never lifted).
    NotWired { files: usize },
    /// At least one workflow runs koala-core. Fine.
    Wired,
}

fn detect_ci_gate(root: &Path) -> CiGateStatus {
    let workflows = root.join(".github/workflows");
    let Ok(entries) = std::fs::read_dir(&workflows) else {
        return CiGateStatus::NoWorkflowsDir;
    };

    let mut wf_files = 0usize;
    let mut wired = false;
    for e in entries.flatten() {
        let p = e.path();
        let is_workflow = matches!(
            p.extension().and_then(|s| s.to_str()),
            Some("yml") | Some("yaml")
        );
        if !is_workflow {
            continue;
        }
        wf_files += 1;
        if let Ok(content) = std::fs::read_to_string(&p) {
            // Match both the published-CLI form (`koala-core check`)
            // and the dogfood form (`cargo run … -p koala-cli -- check`).
            if content.contains("koala-core check") || content.contains("koala-cli -- check") {
                wired = true;
            }
        }
    }

    if wf_files == 0 {
        CiGateStatus::NoWorkflowsDir
    } else if wired {
        CiGateStatus::Wired
    } else {
        CiGateStatus::NotWired { files: wf_files }
    }
}

fn ci_gate_warning(root: &Path) -> Option<String> {
    match detect_ci_gate(root) {
        CiGateStatus::Wired | CiGateStatus::NoWorkflowsDir => None,
        CiGateStatus::NotWired { files } => Some(format!(
            "⚠ CI gate not wired\n  \
             `.github/workflows/` has {files} file(s) but none invoke `koala-core check`.\n  \
             Invariants / drift / artifact gates are NOT enforced on PRs in this repo.\n  \
             Fix: add a workflow that runs `koala-core check --profile strict`. \
             Reference template:\n  \
             {TEMPLATE_WORKFLOW_URL}"
        )),
    }
}

fn count_workspace_crates(root: &Path) -> usize {
    std::fs::read_dir(root.join("crates"))
        .map(|it| {
            it.flatten()
                .filter(|e| e.path().join("Cargo.toml").is_file())
                .count()
        })
        .unwrap_or(0)
}

fn count_artifacts(root: &Path) -> usize {
    let dir = root.join(".review");
    let Ok(rounds) = std::fs::read_dir(&dir) else {
        return 0;
    };
    let mut n = 0;
    for round in rounds.flatten() {
        if !round.file_type().map(|t| t.is_dir()).unwrap_or(false) {
            continue;
        }
        let Ok(files) = std::fs::read_dir(round.path()) else {
            continue;
        };
        for f in files.flatten() {
            if f.path().extension().and_then(|s| s.to_str()) == Some("md") {
                n += 1;
            }
        }
    }
    n
}

fn git_head_oneline(root: &Path) -> Option<String> {
    let out = std::process::Command::new("git")
        .args(["log", "-1", "--oneline"])
        .current_dir(root)
        .output()
        .ok()?;
    if !out.status.success() {
        return None;
    }
    let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
    (!s.is_empty()).then_some(s)
}

fn git_current_branch(root: &Path) -> Option<String> {
    let out = std::process::Command::new("git")
        .args(["rev-parse", "--abbrev-ref", "HEAD"])
        .current_dir(root)
        .output()
        .ok()?;
    if !out.status.success() {
        return None;
    }
    let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
    (!s.is_empty()).then_some(s)
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    fn write(dir: &Path, rel: &str, body: &str) {
        let p = dir.join(rel);
        fs::create_dir_all(p.parent().unwrap()).unwrap();
        fs::write(p, body).unwrap();
    }

    #[test]
    fn no_workflows_dir_is_silent() {
        let tmp = TempDir::new().unwrap();
        assert_eq!(detect_ci_gate(tmp.path()), CiGateStatus::NoWorkflowsDir);
        assert!(ci_gate_warning(tmp.path()).is_none());
    }

    #[test]
    fn workflows_invoking_koala_core_check_are_wired() {
        let tmp = TempDir::new().unwrap();
        write(
            tmp.path(),
            ".github/workflows/koala.yml",
            "name: Koala\njobs:\n  x:\n    steps:\n      - run: koala-core check --profile strict\n",
        );
        assert_eq!(detect_ci_gate(tmp.path()), CiGateStatus::Wired);
        assert!(ci_gate_warning(tmp.path()).is_none());
    }

    #[test]
    fn dogfood_form_via_cargo_run_is_also_wired() {
        // Self-CI uses `cargo run … -p koala-cli -- check` instead of
        // the installed binary; status must accept both.
        let tmp = TempDir::new().unwrap();
        write(
            tmp.path(),
            ".github/workflows/koala.yml",
            "jobs:\n  x:\n    steps:\n      - run: cargo run -p koala-cli -- check\n",
        );
        assert_eq!(detect_ci_gate(tmp.path()), CiGateStatus::Wired);
    }

    #[test]
    fn workflows_without_koala_core_warn() {
        // Reproduces the agent-world failure: a CI workflow exists
        // (npm typecheck + test) but never runs koala-core. Without
        // a warning the project sits in undeclared bootstrap forever.
        let tmp = TempDir::new().unwrap();
        write(
            tmp.path(),
            ".github/workflows/ci.yml",
            "name: CI\njobs:\n  test:\n    steps:\n      - run: npm test\n",
        );
        let status = detect_ci_gate(tmp.path());
        assert_eq!(status, CiGateStatus::NotWired { files: 1 });
        let warn = ci_gate_warning(tmp.path()).expect("warning expected");
        assert!(warn.contains("CI gate not wired"), "{warn}");
        assert!(warn.contains("1 file(s)"), "{warn}");
        assert!(warn.contains("koala-core check"), "{warn}");
    }

    #[test]
    fn ignores_non_workflow_files_in_workflows_dir() {
        // `language-ci.example` (intentional no-`.yml` to keep GH Actions
        // ignoring it until the user renames) shouldn't count as a
        // workflow file.
        let tmp = TempDir::new().unwrap();
        write(
            tmp.path(),
            ".github/workflows/language-ci.example",
            "name: Language CI\n# placeholder\n",
        );
        assert_eq!(detect_ci_gate(tmp.path()), CiGateStatus::NoWorkflowsDir);
    }

    #[test]
    fn yaml_extension_also_recognised() {
        let tmp = TempDir::new().unwrap();
        write(
            tmp.path(),
            ".github/workflows/koala.yaml",
            "jobs:\n  x:\n    steps:\n      - run: koala-core check\n",
        );
        assert_eq!(detect_ci_gate(tmp.path()), CiGateStatus::Wired);
    }

    #[test]
    fn multiple_workflows_one_wired_is_wired() {
        // koala.yml runs the gate; language-ci.yml runs npm tests.
        // That's the recommended split — status should report Wired.
        let tmp = TempDir::new().unwrap();
        write(
            tmp.path(),
            ".github/workflows/koala.yml",
            "jobs:\n  x:\n    steps:\n      - run: koala-core check\n",
        );
        write(
            tmp.path(),
            ".github/workflows/language-ci.yml",
            "jobs:\n  test:\n    steps:\n      - run: npm test\n",
        );
        assert_eq!(detect_ci_gate(tmp.path()), CiGateStatus::Wired);
    }
}
