"""Security-focused integration tests for CLI worktree setup."""

import subprocess
from pathlib import Path

import pytest


@pytest.fixture
def git_repo(tmp_path):
    """Create a temporary git repo for testing real cli._setup_worktree behavior."""
    repo = tmp_path / "test-repo"
    repo.mkdir()
    subprocess.run(["git", "init"], cwd=repo, check=True, capture_output=True)
    subprocess.run(["git", "config", "user.email", "test@test.com"], cwd=repo, check=True, capture_output=True)
    subprocess.run(["git", "config", "user.name", "Test"], cwd=repo, check=True, capture_output=True)
    (repo / "README.md").write_text("# Test Repo\n")
    subprocess.run(["git", "add", "."], cwd=repo, check=True, capture_output=True)
    subprocess.run(["git", "commit", "-m", "Initial commit"], cwd=repo, check=True, capture_output=True)
    return repo


def _force_remove_worktree(info: dict | None) -> None:
    if not info:
        return
    subprocess.run(
        ["git", "worktree", "remove", info["path"], "--force"],
        cwd=info["repo_root"],
        capture_output=True,
        check=False,
    )
    subprocess.run(
        ["git", "branch", "-D", info["branch"]],
        cwd=info["repo_root"],
        capture_output=True,
        check=False,
    )


class TestWorktreeIncludeSecurity:
    def test_rejects_parent_directory_file_traversal(self, git_repo):
        import cli as cli_mod

        outside_file = git_repo.parent / "sensitive.txt"
        outside_file.write_text("SENSITIVE DATA")
        (git_repo / ".worktreeinclude").write_text("../sensitive.txt\n")

        info = None
        try:
            info = cli_mod._setup_worktree(str(git_repo))
            assert info is not None

            wt_path = Path(info["path"])
            assert not (wt_path.parent / "sensitive.txt").exists()
            assert not (wt_path / "../sensitive.txt").resolve().exists()
        finally:
            _force_remove_worktree(info)

    def test_rejects_parent_directory_directory_traversal(self, git_repo):
        import cli as cli_mod

        outside_dir = git_repo.parent / "outside-dir"
        outside_dir.mkdir()
        (outside_dir / "secret.txt").write_text("SENSITIVE DIR DATA")
        (git_repo / ".worktreeinclude").write_text("../outside-dir\n")

        info = None
        try:
            info = cli_mod._setup_worktree(str(git_repo))
            assert info is not None

            wt_path = Path(info["path"])
            escaped_dir = wt_path.parent / "outside-dir"
            assert not escaped_dir.exists()
            assert not escaped_dir.is_symlink()
        finally:
            _force_remove_worktree(info)

    def test_rejects_symlink_that_resolves_outside_repo(self, git_repo):
        import cli as cli_mod

        outside_file = git_repo.parent / "linked-secret.txt"
        outside_file.write_text("LINKED SECRET")
        (git_repo / "leak.txt").symlink_to(outside_file)
        (git_repo / ".worktreeinclude").write_text("leak.txt\n")

        info = None
        try:
            info = cli_mod._setup_worktree(str(git_repo))
            assert info is not None

            assert not (Path(info["path"]) / "leak.txt").exists()
        finally:
            _force_remove_worktree(info)

    def test_allows_valid_file_include(self, git_repo):
        import cli as cli_mod

        (git_repo / ".env").write_text("SECRET=***\n")
        (git_repo / ".worktreeinclude").write_text(".env\n")

        info = None
        try:
            info = cli_mod._setup_worktree(str(git_repo))
            assert info is not None

            copied = Path(info["path"]) / ".env"
            assert copied.exists()
            assert copied.read_text() == "SECRET=***\n"
        finally:
            _force_remove_worktree(info)

    def test_allows_valid_directory_include(self, git_repo):
        import cli as cli_mod

        assets_dir = git_repo / ".venv" / "lib"
        assets_dir.mkdir(parents=True)
        (assets_dir / "marker.txt").write_text("venv marker")
        (git_repo / ".worktreeinclude").write_text(".venv\n")

        info = None
        try:
            info = cli_mod._setup_worktree(str(git_repo))
            assert info is not None

            linked_dir = Path(info["path"]) / ".venv"
            assert linked_dir.is_symlink()
            assert (linked_dir / "lib" / "marker.txt").read_text() == "venv marker"
        finally:
            _force_remove_worktree(info)


class TestWorktreeIncludeEncoding:
    """The include list and .gitignore are UTF-8 files; reading them with the
    locale default breaks Windows (cp1251/GBK mojibake or UnicodeDecodeError,
    swallowed at DEBUG so no include is copied), and a Notepad BOM glues to
    the first line on every platform."""

    def test_bom_in_worktreeinclude_does_not_hide_first_entry(self, git_repo):
        import cli as cli_mod

        (git_repo / ".env").write_text("SECRET=***\n")
        # Notepad-style UTF-8 with BOM; the BOM must not become part of the
        # first entry's path.
        (git_repo / ".worktreeinclude").write_bytes("﻿.env\n".encode("utf-8"))

        info = None
        try:
            info = cli_mod._setup_worktree(str(git_repo))
            assert info is not None
            assert (Path(info["path"]) / ".env").exists()
        finally:
            _force_remove_worktree(info)

    def test_non_ascii_worktreeinclude_entry_copied(self, git_repo):
        import cli as cli_mod

        secret = git_repo / "секреты.env"
        secret.write_text("SECRET=***\n", encoding="utf-8")
        (git_repo / ".worktreeinclude").write_bytes(
            "# ключи агента\nсекреты.env\n".encode("utf-8")
        )

        info = None
        try:
            info = cli_mod._setup_worktree(str(git_repo))
            assert info is not None
            assert (Path(info["path"]) / "секреты.env").exists()
        finally:
            _force_remove_worktree(info)

    def test_bom_in_gitignore_does_not_duplicate_worktrees_entry(self, git_repo):
        import cli as cli_mod

        (git_repo / ".gitignore").write_bytes("﻿.worktrees/\n".encode("utf-8"))

        info = None
        try:
            info = cli_mod._setup_worktree(str(git_repo))
            assert info is not None
            lines = (
                (git_repo / ".gitignore")
                .read_text(encoding="utf-8-sig")
                .splitlines()
            )
            assert lines.count(".worktrees/") == 1, (
                "BOM glued to the first line must not defeat the membership "
                f"check and duplicate the entry: {lines!r}"
            )
        finally:
            _force_remove_worktree(info)
