"""Tests for scrub command helpers and CLI."""

import os
from pathlib import Path

from typer.testing import CliRunner

from repo_sync_kitty.cli import app
from repo_sync_kitty.commands.scrub import IGNORED_NAMES, _find_zombies


class TestFindZombies:
    """Tests for _find_zombies algorithm."""

    def test_git_repo_returns_no_zombies(self, tmp_path: Path) -> None:
        (tmp_path / ".git").mkdir()
        contains_git, zombies = _find_zombies(tmp_path)
        assert contains_git is True
        assert zombies == []

    def test_empty_dir_no_git(self, tmp_path: Path) -> None:
        contains_git, zombies = _find_zombies(tmp_path)
        assert contains_git is False
        assert zombies == []

    def test_loose_file_is_zombie(self, tmp_path: Path) -> None:
        repo = tmp_path / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()
        loose = tmp_path / "stray.txt"
        loose.write_text("orphaned")
        contains_git, zombies = _find_zombies(tmp_path)
        assert contains_git is True
        assert loose in zombies

    def test_dir_without_git_is_zombie(self, tmp_path: Path) -> None:
        repo = tmp_path / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()
        empty_dir = tmp_path / "empty-dir"
        empty_dir.mkdir()
        contains_git, zombies = _find_zombies(tmp_path)
        assert contains_git is True
        assert empty_dir in zombies

    def test_nested_repos_no_zombies(self, tmp_path: Path) -> None:
        group = tmp_path / "libs"
        group.mkdir()
        r1 = group / "repo1"
        r1.mkdir()
        (r1 / ".git").mkdir()
        r2 = group / "repo2"
        r2.mkdir()
        (r2 / ".git").mkdir()
        contains_git, zombies = _find_zombies(tmp_path)
        assert contains_git is True
        assert zombies == []

    def test_ignored_names_skipped(self, tmp_path: Path) -> None:
        repo = tmp_path / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()
        for name in [".DS_Store", ".envrc", "Thumbs.db"]:
            (tmp_path / name).write_text("ignored")
        contains_git, zombies = _find_zombies(tmp_path)
        assert contains_git is True
        assert zombies == []

    def test_permission_error_graceful(self, tmp_path: Path) -> None:
        repo = tmp_path / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()
        restricted = tmp_path / "restricted"
        restricted.mkdir()
        restricted.chmod(0o000)
        try:
            contains_git, zombies = _find_zombies(tmp_path)
            assert contains_git is True
            # restricted dir is classified as zombie (can't verify git content)
            assert restricted in zombies
        finally:
            restricted.chmod(0o755)

    def test_mixed_repos_and_files(self, tmp_path: Path) -> None:
        repo = tmp_path / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()
        stray1 = tmp_path / "notes.txt"
        stray1.write_text("stray")
        stray2 = tmp_path / "orphan-dir"
        stray2.mkdir()
        contains_git, zombies = _find_zombies(tmp_path)
        assert contains_git is True
        assert stray1 in zombies
        assert stray2 in zombies
        assert len(zombies) == 2

    def test_deeply_nested_zombie(self, tmp_path: Path) -> None:
        group = tmp_path / "libs"
        group.mkdir()
        repo = group / "repo1"
        repo.mkdir()
        (repo / ".git").mkdir()
        deep_zombie = group / "abandoned"
        deep_zombie.mkdir()
        contains_git, zombies = _find_zombies(tmp_path)
        assert deep_zombie in zombies


class TestIgnoredNames:
    """Tests for IGNORED_NAMES constant."""

    def test_contains_ds_store(self) -> None:
        assert ".DS_Store" in IGNORED_NAMES

    def test_contains_envrc(self) -> None:
        assert ".envrc" in IGNORED_NAMES

    def test_contains_thumbs_db(self) -> None:
        assert "Thumbs.db" in IGNORED_NAMES


class TestScrubCommand:
    """Tests for scrub CLI command."""

    def test_no_manifest_shows_error(
        self, cli_runner: CliRunner, tmp_path: Path
    ) -> None:
        original = os.getcwd()
        os.chdir(tmp_path)
        try:
            result = cli_runner.invoke(app, ["scrub"])
            assert result.exit_code == 2
        finally:
            os.chdir(original)

    def test_root_path_not_exists(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        manifest = tmp_path / "manifest.toml"
        manifest.write_text("""
[common]
root_path = "/nonexistent/path/to/repos"
branch = "main"
remote = "origin"

[[remotes]]
name = "origin"
base_url = "https://github.com/"
""")
        result = cli_runner.invoke(app, ["scrub", "-m", str(manifest)])
        assert result.exit_code == 2

    def test_no_zombies_found(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        root = tmp_path / "repos"
        root.mkdir()
        repo = root / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()

        manifest = tmp_path / "manifest.toml"
        manifest.write_text(f"""
[common]
root_path = "{root}"
branch = "main"
remote = "origin"

[[remotes]]
name = "origin"
base_url = "https://github.com/"

[[projects]]
slug = "owner/myrepo"
path = "myrepo"
""")
        result = cli_runner.invoke(app, ["scrub", "-m", str(manifest)])
        assert result.exit_code == 0
        assert "no zombie" in result.stdout.lower()

    def test_finds_zombies(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        root = tmp_path / "repos"
        root.mkdir()
        repo = root / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()
        stray = root / "stray.txt"
        stray.write_text("orphaned")

        manifest = tmp_path / "manifest.toml"
        manifest.write_text(f"""
[common]
root_path = "{root}"
branch = "main"
remote = "origin"

[[remotes]]
name = "origin"
base_url = "https://github.com/"

[[projects]]
slug = "owner/myrepo"
path = "myrepo"
""")
        result = cli_runner.invoke(app, ["scrub", "-m", str(manifest)])
        assert result.exit_code == 1
        assert "stray.txt" in result.stdout

    def test_verbose_mode(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        root = tmp_path / "repos"
        root.mkdir()
        repo = root / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()

        manifest = tmp_path / "manifest.toml"
        manifest.write_text(f"""
[common]
root_path = "{root}"
branch = "main"
remote = "origin"

[[remotes]]
name = "origin"
base_url = "https://github.com/"

[[projects]]
slug = "owner/myrepo"
path = "myrepo"
""")
        result = cli_runner.invoke(app, ["scrub", "-m", str(manifest), "-v"])
        assert result.exit_code == 0

    def test_path_override(self, cli_runner: CliRunner, tmp_path: Path) -> None:
        root = tmp_path / "repos"
        root.mkdir()
        custom = tmp_path / "custom"
        custom.mkdir()
        repo = custom / "myrepo"
        repo.mkdir()
        (repo / ".git").mkdir()

        manifest = tmp_path / "manifest.toml"
        manifest.write_text(f"""
[common]
root_path = "{root}"
branch = "main"
remote = "origin"

[[remotes]]
name = "origin"
base_url = "https://github.com/"

[[projects]]
slug = "owner/myrepo"
path = "myrepo"
""")
        result = cli_runner.invoke(
            app, ["scrub", "-m", str(manifest), "-p", str(custom)]
        )
        assert result.exit_code == 0
