"""Tests for pre-commit hooks installation and management."""

import os
import re
import stat
import sys
from pathlib import Path

import pytest
from click.testing import CliRunner

from tripwire.cli import main


@pytest.fixture
def git_repo(tmp_path: Path, monkeypatch) -> Path:
    """Create a temporary git repository."""
    repo_dir = tmp_path / "test_repo"
    repo_dir.mkdir()

    # Initialize git repo
    git_dir = repo_dir / ".git"
    git_dir.mkdir()
    hooks_dir = git_dir / "hooks"
    hooks_dir.mkdir()

    # Change to repo directory
    monkeypatch.chdir(repo_dir)

    return repo_dir


class TestHookInstallation:
    """Tests for hook installation."""

    def test_install_hooks_creates_files(self, git_repo: Path) -> None:
        """Test that install-hooks creates necessary files."""
        runner = CliRunner()

        result = runner.invoke(main, ["install-hooks"])

        assert result.exit_code == 0, f"Command failed with output: {result.output}"
        assert (git_repo / ".git/hooks/pre-commit").exists()
        assert (git_repo / ".tripwire-hooks.toml").exists()

        # Check hook is executable (Unix only - Windows doesn't use executable bits)
        hook_file = git_repo / ".git/hooks/pre-commit"
        if sys.platform != "win32":
            assert hook_file.stat().st_mode & stat.S_IXUSR

    def test_install_hooks_without_git_repo(self, tmp_path: Path, monkeypatch) -> None:
        """Test that install-hooks fails without git repo."""
        # Create temp dir without .git
        non_git_dir = tmp_path / "non_git"
        non_git_dir.mkdir()
        monkeypatch.chdir(non_git_dir)

        runner = CliRunner()
        result = runner.invoke(main, ["install-hooks"])

        assert result.exit_code == 1
        # Normalize output to handle line breaks on Windows
        normalized_output = re.sub(r"\s+", " ", result.output)
        assert "Not a git repository" in normalized_output

    def test_install_hooks_with_existing_hook(self, git_repo: Path) -> None:
        """Test that install-hooks handles existing hooks."""
        runner = CliRunner()

        # Create existing hook
        hook_file = git_repo / ".git/hooks/pre-commit"
        hook_file.write_text("#!/bin/bash\necho 'Existing hook'")

        result = runner.invoke(main, ["install-hooks"])

        assert result.exit_code == 1
        # Normalize output to handle line breaks on Windows
        normalized_output = re.sub(r"\s+", " ", result.output)
        assert "already exists" in normalized_output

    def test_install_hooks_force_overwrite(self, git_repo: Path) -> None:
        """Test force overwrite of existing hooks."""
        runner = CliRunner()

        # Create existing hook
        hook_file = git_repo / ".git/hooks/pre-commit"
        hook_file.write_text("#!/bin/bash\necho 'Old hook'")

        result = runner.invoke(main, ["install-hooks", "--force"])

        assert result.exit_code == 0
        assert hook_file.exists()

        # Check new content
        content = hook_file.read_text()
        assert "Generated by TripWire" in content

    def test_install_hooks_creates_config(self, git_repo: Path) -> None:
        """Test that config file is created with correct content."""
        runner = CliRunner()

        result = runner.invoke(main, ["install-hooks"])

        assert result.exit_code == 0

        config_file = git_repo / ".tripwire-hooks.toml"
        assert config_file.exists()

        content = config_file.read_text()
        assert "schema_validate = true" in content
        assert "secret_scan = true" in content
        assert "drift_check = false" in content

    def test_uninstall_hooks(self, git_repo: Path) -> None:
        """Test uninstalling hooks."""
        runner = CliRunner()

        # Install first
        runner.invoke(main, ["install-hooks"])

        assert (git_repo / ".git/hooks/pre-commit").exists()
        assert (git_repo / ".tripwire-hooks.toml").exists()

        # Uninstall
        result = runner.invoke(main, ["install-hooks", "--uninstall"])

        assert result.exit_code == 0
        assert not (git_repo / ".git/hooks/pre-commit").exists()
        assert not (git_repo / ".tripwire-hooks.toml").exists()

    def test_uninstall_non_tripwire_hook(self, git_repo: Path) -> None:
        """Test uninstalling doesn't remove non-TripWire hooks."""
        runner = CliRunner()

        # Create non-TripWire hook
        hook_file = git_repo / ".git/hooks/pre-commit"
        hook_file.write_text("#!/bin/bash\necho 'Custom hook'")

        result = runner.invoke(main, ["install-hooks", "--uninstall"])

        assert result.exit_code == 0
        assert hook_file.exists()  # Should not be removed
        # Normalize output to handle line breaks on Windows
        normalized_output = re.sub(r"\s+", " ", result.output)
        assert "not managed by TripWire" in normalized_output


class TestPreCommitFramework:
    """Tests for pre-commit framework integration."""

    def test_install_precommit_framework(self, git_repo: Path, monkeypatch) -> None:
        """Test installing pre-commit framework hooks."""
        runner = CliRunner()

        # Mock shutil.which to return a path (simulating pre-commit is installed)
        monkeypatch.setattr("shutil.which", lambda x: "/usr/bin/pre-commit" if x == "pre-commit" else None)

        result = runner.invoke(main, ["install-hooks", "--framework", "pre-commit"])

        assert result.exit_code == 0
        assert (git_repo / ".pre-commit-config.yaml").exists()

        # Check content
        content = (git_repo / ".pre-commit-config.yaml").read_text()
        assert "tripwire schema validate" in content
        assert "tripwire scan" in content

    def test_precommit_framework_not_installed(self, git_repo: Path, monkeypatch) -> None:
        """Test error when pre-commit framework not installed."""
        runner = CliRunner()

        # Mock shutil.which to return None (simulating pre-commit not installed)
        monkeypatch.setattr("shutil.which", lambda x: None)

        result = runner.invoke(main, ["install-hooks", "--framework", "pre-commit"])

        assert result.exit_code == 1
        # Normalize output to handle line breaks on Windows
        normalized_output = re.sub(r"\s+", " ", result.output)
        assert "not installed" in normalized_output

    def test_precommit_existing_config(self, git_repo: Path, monkeypatch) -> None:
        """Test handling existing pre-commit config."""
        runner = CliRunner()

        monkeypatch.setattr("shutil.which", lambda x: "/usr/bin/pre-commit" if x == "pre-commit" else None)

        # Create existing config
        config_file = git_repo / ".pre-commit-config.yaml"
        config_file.write_text("repos: []")

        result = runner.invoke(main, ["install-hooks", "--framework", "pre-commit"])

        assert result.exit_code == 1
        # Normalize output to handle line breaks on Windows
        normalized_output = re.sub(r"\s+", " ", result.output)
        assert "already exists" in normalized_output


class TestHookScriptContent:
    """Tests for hook script content and behavior."""

    def test_hook_script_has_shebang(self, git_repo: Path) -> None:
        """Test that hook script has correct shebang."""
        runner = CliRunner()

        runner.invoke(main, ["install-hooks"])

        hook_file = git_repo / ".git/hooks/pre-commit"
        content = hook_file.read_text()

        assert content.startswith("#!/bin/bash")

    def test_hook_script_checks_config(self, git_repo: Path) -> None:
        """Test that hook script checks for config file."""
        runner = CliRunner()

        runner.invoke(main, ["install-hooks"])

        hook_file = git_repo / ".git/hooks/pre-commit"
        content = hook_file.read_text()

        assert ".tripwire-hooks.toml" in content
        assert "CONFIG_FILE" in content

    def test_hook_script_has_tripwire_commands(self, git_repo: Path) -> None:
        """Test that hook script contains TripWire commands."""
        runner = CliRunner()

        runner.invoke(main, ["install-hooks"])

        hook_file = git_repo / ".git/hooks/pre-commit"
        content = hook_file.read_text()

        assert "tripwire schema validate" in content
        assert "tripwire scan" in content
        assert "tripwire check" in content

    def test_hook_update_preserves_config(self, git_repo: Path) -> None:
        """Test updating hook preserves custom config."""
        runner = CliRunner()

        # Install
        runner.invoke(main, ["install-hooks"])

        # Modify config
        config_file = git_repo / ".tripwire-hooks.toml"
        original_content = config_file.read_text()
        config_file.write_text(original_content + "\n# Custom comment")

        # Update (reinstall with force)
        result = runner.invoke(main, ["install-hooks", "--force"])

        assert result.exit_code == 0

        # Config should still have custom comment (not regenerated)
        # Actually, we always regenerate config if it doesn't exist, not on force
        # So this test should pass because config already exists
