"""Tests for BuildGate quality gate."""

import subprocess
from unittest.mock import MagicMock, patch

from src.gates.build_gate import BuildGate
from src.gates.quality_gate import GateResult


class TestBuildGate:
    """Test suite for BuildGate."""

    def test_check_success(self) -> None:
        """Test that check() returns passed=True when mypy succeeds."""
        # Mock subprocess.run to simulate successful mypy run
        mock_result = MagicMock()
        mock_result.returncode = 0
        mock_result.stdout = "Success: no issues found in 10 source files"
        mock_result.stderr = ""

        with patch("subprocess.run", return_value=mock_result) as mock_run:
            gate = BuildGate()
            result = gate.check()

            # Verify subprocess.run was called with correct arguments
            mock_run.assert_called_once()
            call_args = mock_run.call_args
            assert "mypy" in call_args[0][0]
            assert "src/" in call_args[0][0]

            # Verify result
            assert isinstance(result, GateResult)
            assert result.passed is True
            assert "passed" in result.message.lower()
            assert result.details["return_code"] == 0

    def test_check_failure_type_errors(self) -> None:
        """Test that check() returns passed=False when mypy finds type errors."""
        # Mock subprocess.run to simulate mypy finding errors
        mock_result = MagicMock()
        mock_result.returncode = 1
        mock_result.stdout = ""
        mock_result.stderr = (
            "src/main.py:10: error: Incompatible return value type\n"
            "src/models.py:5: error: Argument 1 has incompatible type\n"
            "Found 2 errors in 2 files (checked 10 source files)"
        )

        with patch("subprocess.run", return_value=mock_result):
            gate = BuildGate()
            result = gate.check()

            # Verify result
            assert isinstance(result, GateResult)
            assert result.passed is False
            assert "failed" in result.message.lower() or "error" in result.message.lower()
            assert result.details["return_code"] == 1
            assert "stderr" in result.details
            assert "2 errors" in result.details["stderr"]

    def test_check_failure_subprocess_error(self) -> None:
        """Test that check() handles subprocess errors gracefully."""
        # Mock subprocess.run to raise CalledProcessError
        with patch(
            "subprocess.run", side_effect=subprocess.CalledProcessError(127, "mypy")
        ):
            gate = BuildGate()
            result = gate.check()

            # Verify result
            assert isinstance(result, GateResult)
            assert result.passed is False
            assert "error" in result.message.lower()
            assert "error" in result.details

    def test_check_failure_file_not_found(self) -> None:
        """Test that check() handles FileNotFoundError when mypy is not installed."""
        # Mock subprocess.run to raise FileNotFoundError
        with patch("subprocess.run", side_effect=FileNotFoundError("mypy not found")):
            gate = BuildGate()
            result = gate.check()

            # Verify result
            assert isinstance(result, GateResult)
            assert result.passed is False
            assert "mypy" in result.message.lower()
            assert "not found" in result.message.lower()
            assert "error" in result.details

    def test_check_uses_strict_mode(self) -> None:
        """Test that check() runs mypy in strict mode."""
        mock_result = MagicMock()
        mock_result.returncode = 0
        mock_result.stdout = "Success: no issues found"
        mock_result.stderr = ""

        with patch("subprocess.run", return_value=mock_result) as mock_run:
            gate = BuildGate()
            gate.check()

            # Verify --strict flag is present
            call_args = mock_run.call_args[0][0]
            # Note: BuildGate uses pyproject.toml config, so we just verify mypy is called
            assert isinstance(call_args, list)
            assert "mypy" in call_args

    def test_check_captures_output(self) -> None:
        """Test that check() captures both stdout and stderr."""
        mock_result = MagicMock()
        mock_result.returncode = 1
        mock_result.stdout = "Some output"
        mock_result.stderr = "Some errors"

        with patch("subprocess.run", return_value=mock_result):
            gate = BuildGate()
            result = gate.check()

            # Verify both stdout and stderr are captured
            assert "stdout" in result.details or "stderr" in result.details
            assert result.details["return_code"] == 1

    def test_check_handles_unexpected_exception(self) -> None:
        """Test that check() handles unexpected exceptions gracefully."""
        # Mock subprocess.run to raise a generic exception
        with patch("subprocess.run", side_effect=RuntimeError("Unexpected error")):
            gate = BuildGate()
            result = gate.check()

            # Verify result
            assert isinstance(result, GateResult)
            assert result.passed is False
            assert "unexpected error" in result.message.lower()
            assert "error" in result.details

    def test_check_uses_timeout(self) -> None:
        """Test that check() sets a timeout on subprocess.run."""
        mock_result = MagicMock()
        mock_result.returncode = 0
        mock_result.stdout = "Success: no issues found"
        mock_result.stderr = ""

        with patch("subprocess.run", return_value=mock_result) as mock_run:
            gate = BuildGate()
            gate.check()

            # Verify timeout is set
            mock_run.assert_called_once()
            call_kwargs = mock_run.call_args[1]
            assert "timeout" in call_kwargs
            assert call_kwargs["timeout"] == 300  # 5 minutes

    def test_check_handles_timeout_exception(self) -> None:
        """Test that check() handles subprocess timeout gracefully."""
        # Mock subprocess.run to raise TimeoutExpired
        with patch(
            "subprocess.run",
            side_effect=subprocess.TimeoutExpired("mypy", timeout=300),
        ):
            gate = BuildGate()
            result = gate.check()

            # Verify result
            assert isinstance(result, GateResult)
            assert result.passed is False
            # TimeoutExpired message contains "timed out after"
            assert "timed out" in result.message.lower() or "timeout" in result.message.lower()
            assert "error" in result.details
