import subprocess
import sys
from pathlib import Path

import pytest

from scix.bootstrap import doctor, perform_dev_up, perform_up
from scix.exceptions import CheckFailedError, ScixError
from scix.generator import sync_workspace
from scix.scaffold import copy_template_paths, copy_template_root


def test_root_ai_tree_matches_packaged_template_ai() -> None:
    repo_root = Path(__file__).resolve().parents[1]
    source_ai = repo_root / "ai"
    template_ai = repo_root / "src/scix/assets/template_root/ai"
    mirrored_roots = ["agents", "hooks", "policy", "skills"]

    source_files = sorted(
        path.relative_to(source_ai)
        for root_name in mirrored_roots
        for path in (source_ai / root_name).rglob("*")
        if path.is_file()
    )
    template_files = sorted(
        path.relative_to(template_ai)
        for root_name in mirrored_roots
        for path in (template_ai / root_name).rglob("*")
        if path.is_file()
    )

    assert source_files == template_files
    for relative_path in source_files:
        assert (source_ai / relative_path).read_text(encoding="utf-8") == (
            template_ai / relative_path
        ).read_text(encoding="utf-8")
    assert (template_ai / "generated/repos/.gitkeep").exists()


def test_sync_workspace_updates_packaged_template_ai_in_source_checkout(tmp_path: Path) -> None:
    copy_template_root(tmp_path)
    copy_template_paths(tmp_path / "src/scix/assets/template_root", ["ai"])
    source_file = tmp_path / "ai/policy/workspace.md"
    template_file = tmp_path / "src/scix/assets/template_root/ai/policy/workspace.md"
    source_file.write_text("updated workspace policy\n", encoding="utf-8")

    with pytest.raises(CheckFailedError):
        sync_workspace(tmp_path, check=True)

    changed = sync_workspace(tmp_path)

    assert template_file.read_text(encoding="utf-8") == "updated workspace policy\n"
    assert template_file in changed


def test_sync_workspace_generates_expected_files(tmp_path: Path) -> None:
    copy_template_root(tmp_path)
    changed = sync_workspace(tmp_path)

    assert changed
    assert (tmp_path / "AGENTS.md").exists()
    assert (tmp_path / "CLAUDE.md").exists()
    assert (tmp_path / ".codex/config.toml").exists()
    assert (tmp_path / ".claude/settings.json").exists()
    assert (tmp_path / ".agents/skills/repo-router/SKILL.md").exists()
    assert (
        (tmp_path / ".agents/skills/repo-router/SKILL.md")
        .read_text(encoding="utf-8")
        .startswith("---\n")
    )
    assert (tmp_path / ".claude/agents/explorer.md").exists()
    assert (tmp_path / ".codex/agents/explorer.toml").exists()
    assert (tmp_path / ".claude/agents/tester.md").exists()
    assert (tmp_path / ".codex/agents/tester.toml").exists()
    assert (tmp_path / "ai/generated/repos/kintera/AGENTS.md").exists()
    assert "implementer -> tester -> reviewer" in (tmp_path / "AGENTS.md").read_text(
        encoding="utf-8"
    )


def test_sync_workspace_copies_repo_overlays_into_existing_clone(tmp_path: Path) -> None:
    copy_template_root(tmp_path)
    (tmp_path / "repos/kintera").mkdir(parents=True)

    sync_workspace(tmp_path)

    assert (tmp_path / "repos/kintera/AGENTS.md").exists()
    assert (tmp_path / "repos/kintera/CLAUDE.md").exists()
    overlay_text = (tmp_path / "repos/kintera/AGENTS.md").read_text(encoding="utf-8")
    assert "exact validation command and result" in overlay_text


def test_sync_workspace_check_detects_stale_file(tmp_path: Path) -> None:
    copy_template_root(tmp_path)
    sync_workspace(tmp_path)
    (tmp_path / "AGENTS.md").write_text("changed\n", encoding="utf-8")

    with pytest.raises(CheckFailedError):
        sync_workspace(tmp_path, check=True)


def test_perform_up_rejects_non_empty_directory_without_force(tmp_path: Path) -> None:
    (tmp_path / "note.txt").write_text("hello\n", encoding="utf-8")

    with pytest.raises(ScixError):
        perform_up(tmp_path, assume_yes=True, skip_repos=True)


def test_perform_up_rejects_repo_shaped_directory_without_force(tmp_path: Path) -> None:
    (tmp_path / "README.md").write_text("developer checkout\n", encoding="utf-8")
    (tmp_path / "src/scix").mkdir(parents=True)

    with pytest.raises(ScixError):
        perform_up(tmp_path, assume_yes=True, skip_repos=True)


def test_perform_up_allows_directory_with_only_xenv(tmp_path: Path) -> None:
    (tmp_path / "xenv").mkdir()

    perform_up(tmp_path, assume_yes=True, skip_repos=True)

    assert (tmp_path / ".ai-root").exists()
    assert (tmp_path / "README.md").exists()


def test_perform_up_force_allows_non_empty_directory(tmp_path: Path) -> None:
    (tmp_path / "note.txt").write_text("hello\n", encoding="utf-8")

    perform_up(tmp_path, assume_yes=True, force=True, skip_repos=True)

    assert (tmp_path / ".ai-root").exists()
    assert (tmp_path / "README.md").exists()


def test_doctor_reports_invalid_skill_frontmatter(tmp_path: Path) -> None:
    copy_template_root(tmp_path)
    skill_path = tmp_path / "ai/skills/repo-router/SKILL.md"
    skill_path.write_text("# broken\n", encoding="utf-8")

    issues = doctor(tmp_path)

    assert any("Invalid skill file" in issue for issue in issues)
    assert not any("xenv" in issue for issue in issues)
    assert not any("pyenv" in issue for issue in issues)
    assert not any("sudo" in issue for issue in issues)


def test_perform_dev_up_backfills_workspace_without_overwriting_canonical_files(
    tmp_path: Path,
) -> None:
    copy_template_paths(tmp_path, ["ai"])
    (tmp_path / "src/scix").mkdir(parents=True)
    (tmp_path / "tests").mkdir()
    (tmp_path / "docs").mkdir()
    (tmp_path / ".github/workflows").mkdir(parents=True)
    (tmp_path / "pyproject.toml").write_text("[project]\nname = 'scix'\n", encoding="utf-8")
    (tmp_path / "README.md").write_text("custom contributor README\n", encoding="utf-8")
    (tmp_path / "ai/policy/workspace.md").write_text("custom workspace policy\n", encoding="utf-8")

    perform_dev_up(tmp_path, skip_repos=True)

    assert (tmp_path / "README.md").read_text(encoding="utf-8") == "custom contributor README\n"
    assert (tmp_path / "ai/policy/workspace.md").read_text(
        encoding="utf-8"
    ) == "custom workspace policy\n"
    assert (tmp_path / ".ai-root").exists()
    assert (tmp_path / "repos/README.md").exists()
    assert (tmp_path / "workspace/README.md").exists()
    assert (tmp_path / ".codex/config.toml").exists()
    assert (tmp_path / ".claude/settings.json").exists()


def test_dev_setup_and_bootstrap_import_without_yaml_on_sys_path() -> None:
    repo_root = Path(__file__).resolve().parents[1]
    code = (
        "import sys; "
        f"sys.path.insert(0, {str(repo_root / 'src').__repr__()}); "
        "import scix.bootstrap; "
        "import scix.dev_setup"
    )
    result = subprocess.run(
        [sys.executable, "-S", "-c", code],
        cwd=repo_root,
        capture_output=True,
        text=True,
        check=False,
    )

    assert result.returncode == 0, result.stderr


def test_runtime_bootstrap_sources_do_not_reference_pyenv_or_sudo() -> None:
    repo_root = Path(__file__).resolve().parents[1]
    for relative_path in [
        Path("src/scix/bootstrap.py"),
        Path("src/scix/cli.py"),
        Path("src/scix/dev_setup.py"),
        Path("src/scix/constants.py"),
    ]:
        content = (repo_root / relative_path).read_text(encoding="utf-8").lower()
        assert "pyenv" not in content
        assert "sudo" not in content
