//! Onboarding detection — checks whether scaffold templates have been customized.
use std::path::Path;

/// Sentinel comment injected as the first line of scaffold templates.
/// Only untouched templates contain this marker — real project content
/// will never include it, so it avoids false positives when the project
/// itself is an "Agentic AI Code Assistant".
pub(crate) const TEMPLATE_SENTINEL: &str = "<!-- huskies:scaffold-template -->";

/// Marker found in the default `script/test` scaffold output.
const TEMPLATE_MARKER_SCRIPT: &str = "No tests configured";

/// Summary of what parts of a project still need onboarding.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OnboardingStatus {
    /// True when the project context spec needs to be populated.
    pub needs_context: bool,
    /// True when the tech stack spec needs to be populated.
    pub needs_stack: bool,
    /// True when `script/test` still contains the scaffold placeholder.
    pub needs_test_script: bool,
    /// True when `.huskies/project.toml` is missing or has no
    /// `[[component]]` entries.
    pub needs_project_toml: bool,
}

impl OnboardingStatus {
    /// Returns `true` when any onboarding step is still needed.
    pub fn needs_onboarding(&self) -> bool {
        self.needs_context || self.needs_stack
    }
}

/// Inspect the project at `project_root` and determine which onboarding
/// steps are still required.
pub fn check_onboarding_status(project_root: &Path) -> OnboardingStatus {
    let story_kit = project_root.join(".huskies");

    OnboardingStatus {
        needs_context: is_template_or_missing(
            &story_kit.join("specs").join("00_CONTEXT.md"),
            TEMPLATE_SENTINEL,
        ),
        needs_stack: is_template_or_missing(
            &story_kit.join("specs").join("tech").join("STACK.md"),
            TEMPLATE_SENTINEL,
        ),
        needs_test_script: is_template_or_missing(
            &project_root.join("script").join("test"),
            TEMPLATE_MARKER_SCRIPT,
        ),
        needs_project_toml: needs_project_toml(&story_kit),
    }
}

/// Returns `true` when the file is missing, empty, or contains the
/// given scaffold marker string.
fn is_template_or_missing(path: &Path, marker: &str) -> bool {
    match std::fs::read_to_string(path) {
        Ok(content) => content.trim().is_empty() || content.contains(marker),
        Err(_) => true,
    }
}

/// Returns `true` when `project.toml` is missing or has no
/// `[[component]]` entries.
fn needs_project_toml(story_kit: &Path) -> bool {
    let toml_path = story_kit.join("project.toml");
    match std::fs::read_to_string(toml_path) {
        Ok(content) => !content.contains("[[component]]"),
        Err(_) => true,
    }
}

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

    // ── needs_onboarding ──────────────────────────────────────────

    #[test]
    fn needs_onboarding_true_when_no_files_exist() {
        let dir = TempDir::new().unwrap();
        let root = dir.path().to_path_buf();
        let status = check_onboarding_status(&root);
        assert!(status.needs_onboarding());
        assert!(status.needs_context);
        assert!(status.needs_stack);
        assert!(status.needs_test_script);
        assert!(status.needs_project_toml);
    }

    #[test]
    fn needs_onboarding_true_when_specs_contain_scaffold_sentinel() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        // Write content that includes the scaffold sentinel
        fs::write(
            root.join(".huskies/specs/00_CONTEXT.md"),
            "<!-- huskies:scaffold-template -->\n# Project Context\nPlaceholder...",
        )
        .unwrap();
        fs::write(
            root.join(".huskies/specs/tech/STACK.md"),
            "<!-- huskies:scaffold-template -->\n# Tech Stack\nPlaceholder...",
        )
        .unwrap();

        let status = check_onboarding_status(&root);
        assert!(status.needs_context);
        assert!(status.needs_stack);
        assert!(status.needs_onboarding());
    }

    #[test]
    fn needs_onboarding_false_when_content_mentions_agentic_but_no_sentinel() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        // Real project content that happens to mention "Agentic AI Code Assistant"
        // but does NOT contain the scaffold sentinel — should NOT trigger onboarding.
        fs::write(
            root.join(".huskies/specs/00_CONTEXT.md"),
            "# Project Context\nTo build a standalone Agentic AI Code Assistant application.",
        )
        .unwrap();
        fs::write(
            root.join(".huskies/specs/tech/STACK.md"),
            "# Tech Stack\nThis is an Agentic Code Assistant binary.",
        )
        .unwrap();

        let status = check_onboarding_status(&root);
        assert!(!status.needs_context);
        assert!(!status.needs_stack);
        assert!(!status.needs_onboarding());
    }

    #[test]
    fn needs_onboarding_false_when_specs_have_custom_content() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        fs::write(
            root.join(".huskies/specs/00_CONTEXT.md"),
            "# My Project\n\nThis is an e-commerce platform for selling widgets.",
        )
        .unwrap();
        fs::write(
            root.join(".huskies/specs/tech/STACK.md"),
            "# Tech Stack\n\n## Backend: Python + FastAPI\n## Frontend: React + TypeScript",
        )
        .unwrap();

        let status = check_onboarding_status(&root);
        assert!(!status.needs_context);
        assert!(!status.needs_stack);
        assert!(!status.needs_onboarding());
    }

    #[test]
    fn needs_onboarding_true_when_specs_are_empty() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        fs::write(root.join(".huskies/specs/00_CONTEXT.md"), "  \n").unwrap();
        fs::write(root.join(".huskies/specs/tech/STACK.md"), "").unwrap();

        let status = check_onboarding_status(&root);
        assert!(status.needs_context);
        assert!(status.needs_stack);
    }

    // ── needs_test_script ─────────────────────────────────────────

    #[test]
    fn needs_test_script_true_when_placeholder() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        fs::write(
            root.join("script/test"),
            "#!/usr/bin/env bash\nset -euo pipefail\necho \"No tests configured\"\n",
        )
        .unwrap();

        let status = check_onboarding_status(&root);
        assert!(status.needs_test_script);
    }

    #[test]
    fn needs_test_script_false_when_customised() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        fs::write(
            root.join("script/test"),
            "#!/usr/bin/env bash\nset -euo pipefail\ncargo test\n",
        )
        .unwrap();

        let status = check_onboarding_status(&root);
        assert!(!status.needs_test_script);
    }

    // ── needs_project_toml ────────────────────────────────────────

    #[test]
    fn needs_project_toml_true_when_missing() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        let status = check_onboarding_status(&root);
        assert!(status.needs_project_toml);
    }

    #[test]
    fn needs_project_toml_true_when_no_components() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        fs::write(root.join(".huskies/project.toml"), "# empty config\n").unwrap();

        let status = check_onboarding_status(&root);
        assert!(status.needs_project_toml);
    }

    #[test]
    fn needs_project_toml_false_when_has_components() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        fs::write(
            root.join(".huskies/project.toml"),
            "[[component]]\nname = \"app\"\npath = \".\"\nsetup = [\"cargo check\"]\n",
        )
        .unwrap();

        let status = check_onboarding_status(&root);
        assert!(!status.needs_project_toml);
    }

    // ── CLAUDE.md is not an onboarding step ──────────────────────

    #[test]
    fn onboarding_status_does_not_check_claude_md() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        // Write real content for the required onboarding files
        fs::write(
            root.join(".huskies/specs/00_CONTEXT.md"),
            "# My Project\n\nReal project context.",
        )
        .unwrap();
        fs::write(
            root.join(".huskies/specs/tech/STACK.md"),
            "# My Stack\n\nReal stack content.",
        )
        .unwrap();

        // CLAUDE.md is absent — should NOT affect onboarding result
        assert!(!root.join("CLAUDE.md").exists());

        let status = check_onboarding_status(&root);
        assert!(
            !status.needs_context,
            "needs_context should be false with real content"
        );
        assert!(
            !status.needs_stack,
            "needs_stack should be false with real content"
        );
        assert!(
            !status.needs_onboarding(),
            "needs_onboarding() should be false regardless of CLAUDE.md presence"
        );
    }

    // ── partial onboarding ────────────────────────────────────────

    #[test]
    fn needs_onboarding_true_when_only_context_is_template() {
        let dir = TempDir::new().unwrap();
        let root = setup_project(&dir);

        // Context still has sentinel
        fs::write(
            root.join(".huskies/specs/00_CONTEXT.md"),
            "<!-- huskies:scaffold-template -->\n# Project Context\nPlaceholder...",
        )
        .unwrap();
        // Stack is customised (no sentinel)
        fs::write(
            root.join(".huskies/specs/tech/STACK.md"),
            "# My Stack\nRuby on Rails + PostgreSQL",
        )
        .unwrap();

        let status = check_onboarding_status(&root);
        assert!(status.needs_context);
        assert!(!status.needs_stack);
        assert!(status.needs_onboarding());
    }
}
