"""
Tests for build_info CLI tool.

Tests the git commit info generator used by git hooks and CI/CD.
"""

import subprocess
from unittest.mock import MagicMock, patch

import pytest

from appinfra.version.build_info import generate, get_git_commit, main

# =============================================================================
# Test get_git_commit
# =============================================================================


@pytest.mark.unit
class TestGetGitCommit:
    """Test get_git_commit function."""

    @patch("subprocess.run")
    def test_returns_commit_info(self, mock_run):
        """Test returns commit hash and message."""

        def run_side_effect(args, **kwargs):
            if "rev-parse" in args:
                return MagicMock(
                    returncode=0,
                    stdout="abc123def456789012345678901234567890abcd\n",
                )
            elif "--format=%s" in args:
                return MagicMock(returncode=0, stdout="Test commit message\n")
            return MagicMock(returncode=1)

        mock_run.side_effect = run_side_effect

        result = get_git_commit()

        assert result is not None
        full, short, message = result
        assert full == "abc123def456789012345678901234567890abcd"
        assert short == "abc123d"
        assert message == "Test commit message"

    @patch("subprocess.run")
    def test_returns_none_on_git_failure(self, mock_run):
        """Test returns None when git command fails."""
        mock_run.return_value = MagicMock(returncode=1)

        result = get_git_commit()
        assert result is None

    @patch("subprocess.run")
    def test_handles_subprocess_error(self, mock_run):
        """Test handles subprocess errors gracefully."""
        mock_run.side_effect = subprocess.SubprocessError("git error")

        result = get_git_commit()
        assert result is None

    @patch("subprocess.run")
    def test_handles_file_not_found(self, mock_run):
        """Test handles missing git command."""
        mock_run.side_effect = FileNotFoundError("git not found")

        result = get_git_commit()
        assert result is None

    @patch("subprocess.run")
    def test_handles_empty_commit_message(self, mock_run):
        """Test handles empty commit message."""

        def run_side_effect(args, **kwargs):
            if "rev-parse" in args:
                return MagicMock(returncode=0, stdout="abc123\n")
            elif "--format=%s" in args:
                return MagicMock(returncode=1)  # message command fails
            return MagicMock(returncode=1)

        mock_run.side_effect = run_side_effect

        result = get_git_commit()
        assert result is not None
        _, _, message = result
        assert message == ""


# =============================================================================
# Test generate
# =============================================================================


@pytest.mark.unit
class TestGenerate:
    """Test generate function."""

    @patch("appinfra.version.build_info.get_git_commit")
    def test_generates_build_info_file(self, mock_get_commit, tmp_path):
        """Test generates _build_info.py file."""
        mock_get_commit.return_value = (
            "abc123def456789012345678901234567890abcd",
            "abc123d",
            "Test commit",
        )

        result = generate(tmp_path)

        assert result is True
        build_info = tmp_path / "_build_info.py"
        assert build_info.exists()
        content = build_info.read_text()
        assert 'COMMIT_HASH = "abc123def456789012345678901234567890abcd"' in content
        assert 'COMMIT_SHORT = "abc123d"' in content
        assert 'COMMIT_MESSAGE = "Test commit"' in content
        assert "BUILD_TIME" in content

    @patch("appinfra.version.build_info.get_git_commit")
    def test_returns_false_without_git(self, mock_get_commit, tmp_path):
        """Test returns False when not in git repo."""
        mock_get_commit.return_value = None

        result = generate(tmp_path)
        assert result is False

    @patch("appinfra.version.build_info.get_git_commit")
    def test_escapes_quotes_in_message(self, mock_get_commit, tmp_path):
        """Test escapes quotes in commit message."""
        mock_get_commit.return_value = (
            "abc123def456789012345678901234567890abcd",
            "abc123d",
            'Fix "bug" in code',
        )

        generate(tmp_path)

        content = (tmp_path / "_build_info.py").read_text()
        assert 'COMMIT_MESSAGE = "Fix \\"bug\\" in code"' in content

    @patch("appinfra.version.build_info.get_git_commit")
    def test_escapes_backslashes_in_message(self, mock_get_commit, tmp_path):
        """Test escapes backslashes in commit message."""
        mock_get_commit.return_value = (
            "abc123def456789012345678901234567890abcd",
            "abc123d",
            "Fix path\\to\\file",
        )

        generate(tmp_path)

        content = (tmp_path / "_build_info.py").read_text()
        assert 'COMMIT_MESSAGE = "Fix path\\\\to\\\\file"' in content


# =============================================================================
# Test main
# =============================================================================


@pytest.mark.unit
class TestMain:
    """Test main CLI entry point."""

    @patch("sys.argv", ["build_info"])
    def test_no_args_returns_1(self, capsys):
        """Test returns 1 with no arguments."""
        result = main()
        assert result == 1
        captured = capsys.readouterr()
        assert "Usage:" in captured.out

    @patch("sys.argv", ["build_info", "/nonexistent/path"])
    def test_invalid_path_returns_1(self, capsys):
        """Test returns 1 with invalid path."""
        result = main()
        assert result == 1
        captured = capsys.readouterr()
        assert "Not a directory" in captured.err

    @patch("appinfra.version.build_info.generate")
    @patch("sys.argv", ["build_info", "."])
    def test_success_returns_0(self, mock_generate):
        """Test returns 0 on success."""
        mock_generate.return_value = True
        result = main()
        assert result == 0

    @patch("appinfra.version.build_info.generate")
    @patch("sys.argv", ["build_info", "."])
    def test_failure_returns_1(self, mock_generate):
        """Test returns 1 on generate failure."""
        mock_generate.return_value = False
        result = main()
        assert result == 1
