"""
Module: test_claude_env
Description: Tests for shared Claude Code environment setup

Implements:
    - docs/cli/reference.md#huitzo-pack-new
    - docs/cli/reference.md#huitzo-dashboard-new
"""

import json
import shutil
from pathlib import Path
from unittest.mock import patch

from huitzo_cli.claude_env import _apply_profile, _select_profile


def _create_fake_repo(tmp_path: Path) -> Path:
    """Create a fake pack-claude-env repo structure for testing."""
    repo = tmp_path / "repo"
    claude = repo / "claude"

    # Agents
    agents = claude / "agents"
    agents.mkdir(parents=True)
    (agents / "docs-writer.md").write_text("# Docs Writer")
    (agents / "pack-developer.md").write_text("# Pack Developer")
    (agents / "pack-reviewer.md").write_text("# Pack Reviewer")
    (agents / "dashboard-developer.md").write_text("# Dashboard Developer")
    (agents / "dashboard-reviewer.md").write_text("# Dashboard Reviewer")
    (agents / "spec-architect.md").write_text("# Spec Architect")

    # Rules
    rules = claude / "rules"
    rules.mkdir(parents=True)
    (rules / "sdk-patterns.md").write_text("# SDK Patterns")
    (rules / "error-handling.md").write_text("# Error Handling")
    (rules / "hub-contract.md").write_text("# Hub Contract")
    (rules / "react-patterns.md").write_text("# React Patterns")
    (rules / "dashboard-manifest.md").write_text("# Dashboard Manifest")
    (rules / "documentation.md").write_text("# Documentation")
    (rules / "testing.md").write_text("# Testing")
    (rules / "traceability.md").write_text("# Traceability")

    # Skills
    for skill in [
        "draft-docs",
        "add-command",
        "test-pack",
        "validate-pack",
        "scaffold-dashboard",
        "validate-dashboard",
        "test-dashboard",
        "dashboard-dev",
        "lint-and-fix",
    ]:
        skill_dir = claude / "skills" / skill
        skill_dir.mkdir(parents=True)
        (skill_dir / "SKILL.md").write_text(f"# /{skill}")

    draft_spec = claude / "skills" / "draft-spec"
    draft_spec.mkdir(parents=True)
    (draft_spec / "SKILL.md").write_text("# /draft-spec")
    templates = draft_spec / "templates"
    templates.mkdir()
    (templates / "spec-template.md.tmpl").write_text("template")
    (templates / "questionnaire.md.tmpl").write_text("questions")

    # CLAUDE.md and settings
    (claude / "CLAUDE.md").write_text("# CLAUDE.md")
    (claude / "settings.json").write_text("{}")

    # Profiles
    profiles = repo / "profiles"
    profiles.mkdir()
    (profiles / "pack-only.json").write_text(
        json.dumps(
            {
                "name": "pack-only",
                "files": {
                    "exclude": [
                        "claude/agents/dashboard-developer.md",
                        "claude/agents/dashboard-reviewer.md",
                        "claude/rules/hub-contract.md",
                        "claude/rules/react-patterns.md",
                        "claude/rules/dashboard-manifest.md",
                        "claude/skills/scaffold-dashboard/",
                        "claude/skills/validate-dashboard/",
                        "claude/skills/test-dashboard/",
                        "claude/skills/dashboard-dev/",
                    ],
                },
            }
        )
    )
    (profiles / "dashboard-only.json").write_text(
        json.dumps(
            {
                "name": "dashboard-only",
                "files": {
                    "exclude": [
                        "claude/agents/pack-developer.md",
                        "claude/agents/pack-reviewer.md",
                        "claude/rules/sdk-patterns.md",
                        "claude/rules/error-handling.md",
                        "claude/skills/draft-docs/",
                        "claude/skills/add-command/",
                        "claude/skills/test-pack/",
                        "claude/skills/validate-pack/",
                    ],
                },
            }
        )
    )
    (profiles / "full-stack.json").write_text(
        json.dumps({"name": "full-stack", "files": {"include": ["*"], "exclude": []}})
    )

    return repo


# --- _apply_profile tests ---


def test_apply_profile_full_stack_copies_everything(tmp_path: Path) -> None:
    """Test full-stack profile copies all files."""
    repo = _create_fake_repo(tmp_path)
    project = tmp_path / "project"
    project.mkdir()

    _apply_profile(repo, project, "full-stack")

    dst = project / ".claude"
    assert (dst / "CLAUDE.md").exists()
    assert (dst / "agents" / "pack-developer.md").exists()
    assert (dst / "agents" / "dashboard-developer.md").exists()
    assert (dst / "rules" / "sdk-patterns.md").exists()
    assert (dst / "rules" / "hub-contract.md").exists()
    assert (dst / "skills" / "add-command" / "SKILL.md").exists()
    assert (dst / "skills" / "scaffold-dashboard" / "SKILL.md").exists()


def test_apply_profile_pack_only_excludes_dashboard_files(tmp_path: Path) -> None:
    """Test pack-only profile excludes dashboard agents, rules, and skills."""
    repo = _create_fake_repo(tmp_path)
    project = tmp_path / "project"
    project.mkdir()

    _apply_profile(repo, project, "pack-only")

    dst = project / ".claude"

    # Pack files present
    assert (dst / "CLAUDE.md").exists()
    assert (dst / "agents" / "pack-developer.md").exists()
    assert (dst / "agents" / "pack-reviewer.md").exists()
    assert (dst / "agents" / "docs-writer.md").exists()
    assert (dst / "rules" / "sdk-patterns.md").exists()
    assert (dst / "rules" / "documentation.md").exists()
    assert (dst / "skills" / "add-command" / "SKILL.md").exists()
    assert (dst / "skills" / "test-pack" / "SKILL.md").exists()
    assert (dst / "skills" / "lint-and-fix" / "SKILL.md").exists()
    assert (dst / "skills" / "draft-spec" / "SKILL.md").exists()

    # Dashboard files excluded
    assert not (dst / "agents" / "dashboard-developer.md").exists()
    assert not (dst / "agents" / "dashboard-reviewer.md").exists()
    assert not (dst / "rules" / "hub-contract.md").exists()
    assert not (dst / "rules" / "react-patterns.md").exists()
    assert not (dst / "rules" / "dashboard-manifest.md").exists()
    assert not (dst / "skills" / "scaffold-dashboard").exists()
    assert not (dst / "skills" / "validate-dashboard").exists()
    assert not (dst / "skills" / "test-dashboard").exists()
    assert not (dst / "skills" / "dashboard-dev").exists()


def test_apply_profile_dashboard_only_excludes_pack_files(tmp_path: Path) -> None:
    """Test dashboard-only profile excludes pack agents, rules, and skills."""
    repo = _create_fake_repo(tmp_path)
    project = tmp_path / "project"
    project.mkdir()

    _apply_profile(repo, project, "dashboard-only")

    dst = project / ".claude"

    # Dashboard files present
    assert (dst / "CLAUDE.md").exists()
    assert (dst / "agents" / "dashboard-developer.md").exists()
    assert (dst / "agents" / "dashboard-reviewer.md").exists()
    assert (dst / "agents" / "docs-writer.md").exists()
    assert (dst / "rules" / "hub-contract.md").exists()
    assert (dst / "rules" / "react-patterns.md").exists()
    assert (dst / "skills" / "scaffold-dashboard" / "SKILL.md").exists()
    assert (dst / "skills" / "test-dashboard" / "SKILL.md").exists()
    assert (dst / "skills" / "lint-and-fix" / "SKILL.md").exists()
    assert (dst / "skills" / "draft-spec" / "SKILL.md").exists()

    # Pack files excluded
    assert not (dst / "agents" / "pack-developer.md").exists()
    assert not (dst / "agents" / "pack-reviewer.md").exists()
    assert not (dst / "rules" / "sdk-patterns.md").exists()
    assert not (dst / "rules" / "error-handling.md").exists()
    assert not (dst / "skills" / "draft-docs").exists()
    assert not (dst / "skills" / "add-command").exists()
    assert not (dst / "skills" / "test-pack").exists()
    assert not (dst / "skills" / "validate-pack").exists()


def test_apply_profile_prefix_matching_does_not_over_exclude(tmp_path: Path) -> None:
    """Test that excluding 'dashboard-dev/' does not exclude 'dashboard-developer.md'.

    Regression test for prefix-matching bug where 'dashboard-dev' would match
    'dashboard-developer' because of startswith without path separator.
    """
    repo = _create_fake_repo(tmp_path)
    project = tmp_path / "project"
    project.mkdir()

    # pack-only excludes dashboard-dev/ but NOT dashboard-developer.md
    # (dashboard-developer.md is excluded by its own explicit pattern)
    # To test the boundary, create a custom profile that only excludes dashboard-dev/
    custom_profile = repo / "profiles" / "test-prefix.json"
    custom_profile.write_text(
        json.dumps(
            {
                "name": "test-prefix",
                "files": {"exclude": ["claude/skills/dashboard-dev/"]},
            }
        )
    )

    src_claude = repo / "claude"
    dst_claude = project / ".claude"

    profile_data = json.loads(custom_profile.read_text())
    exclude_patterns = profile_data.get("files", {}).get("exclude", [])

    def _ignore_fn(directory: str, contents: list[str]) -> list[str]:
        ignored = []
        rel_dir = Path(directory).relative_to(src_claude)
        for name in contents:
            rel_path = str(rel_dir / name) if str(rel_dir) != "." else name
            full_rel = f"claude/{rel_path}"
            for pattern in exclude_patterns:
                stripped = pattern.rstrip("/")
                if full_rel == stripped or full_rel.startswith(stripped + "/"):
                    ignored.append(name)
                    break
        return ignored

    shutil.copytree(src_claude, dst_claude, ignore=_ignore_fn)

    # dashboard-dev/ should be excluded
    assert not (dst_claude / "skills" / "dashboard-dev").exists()
    # But dashboard-developer.md (similar prefix) should NOT be excluded
    assert (dst_claude / "agents" / "dashboard-developer.md").exists()


def test_apply_profile_dirs_exist_ok(tmp_path: Path) -> None:
    """Test that re-applying a profile to an existing .claude/ works."""
    repo = _create_fake_repo(tmp_path)
    project = tmp_path / "project"
    project.mkdir()

    # First apply
    _apply_profile(repo, project, "full-stack")
    assert (project / ".claude" / "CLAUDE.md").exists()

    # Second apply should not raise FileExistsError
    _apply_profile(repo, project, "full-stack")
    assert (project / ".claude" / "CLAUDE.md").exists()


# --- _select_profile tests ---


def test_select_profile_numeric_input() -> None:
    """Test selecting profile by number."""
    with patch("huitzo_cli.claude_env.typer.prompt", return_value="1"):
        result = _select_profile("full-stack")
    assert result == "pack-only"

    with patch("huitzo_cli.claude_env.typer.prompt", return_value="2"):
        result = _select_profile("full-stack")
    assert result == "dashboard-only"

    with patch("huitzo_cli.claude_env.typer.prompt", return_value="3"):
        result = _select_profile("full-stack")
    assert result == "full-stack"


def test_select_profile_name_input() -> None:
    """Test selecting profile by name string."""
    with patch("huitzo_cli.claude_env.typer.prompt", return_value="pack-only"):
        result = _select_profile("full-stack")
    assert result == "pack-only"

    with patch("huitzo_cli.claude_env.typer.prompt", return_value="dashboard-only"):
        result = _select_profile("full-stack")
    assert result == "dashboard-only"


def test_select_profile_default() -> None:
    """Test that pressing enter uses the default profile."""
    with patch("huitzo_cli.claude_env.typer.prompt", return_value="2"):
        result = _select_profile("dashboard-only")
    assert result == "dashboard-only"


def test_select_profile_invalid_falls_back() -> None:
    """Test that invalid input falls back to the default profile."""
    with patch("huitzo_cli.claude_env.typer.prompt", return_value="99"):
        result = _select_profile("pack-only")
    assert result == "pack-only"

    with patch("huitzo_cli.claude_env.typer.prompt", return_value="garbage"):
        result = _select_profile("dashboard-only")
    assert result == "dashboard-only"
