"""Tests for TestGate quality gate."""

import subprocess
from unittest.mock import MagicMock, patch

from src.gates.quality_gate import GateResult
from src.gates.test_gate import TestGate


class TestTestGate:
    """Test suite for TestGate."""

    def test_check_success_all_tests_pass(self) -> None:
        """Test that check() returns passed=True when all tests pass."""
        # Mock subprocess.run to simulate all tests passing
        mock_result = MagicMock()
        mock_result.returncode = 0
        mock_result.stdout = (
            "============================= test session starts ==============================\n"
            "collected 50 items\n"
            "tests/test_example.py .................................................. [100%]\n"
            "============================== 50 passed in 2.34s ===============================\n"
        )
        mock_result.stderr = ""

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

            # Verify subprocess.run was called with correct arguments
            mock_run.assert_called_once()
            call_args = mock_run.call_args
            assert "pytest" in call_args[0][0] or "python" 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_tests_fail(self) -> None:
        """Test that check() returns passed=False when any test fails."""
        # Mock subprocess.run to simulate test failures
        mock_result = MagicMock()
        mock_result.returncode = 1
        mock_result.stdout = (
            "============================= test session starts ==============================\n"
            "collected 50 items\n"
            "tests/test_example.py F................................................ [100%]\n"
            "=================================== FAILURES ===================================\n"
            "________________________________ test_something ________________________________\n"
            "AssertionError: expected True but got False\n"
            "========================= 1 failed, 49 passed in 2.34s =========================\n"
        )
        mock_result.stderr = ""

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

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

    def test_check_requires_100_percent_pass_rate(self) -> None:
        """Test that check() requires 100% test pass rate (non-negotiable)."""
        # Mock subprocess.run to simulate 99% pass rate (1 failure out of 100)
        mock_result = MagicMock()
        mock_result.returncode = 1
        mock_result.stdout = "1 failed, 99 passed in 5.0s"
        mock_result.stderr = ""

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

            # Verify result - even 99% is not acceptable
            assert isinstance(result, GateResult)
            assert result.passed is False
            assert "failed" in result.message.lower()

    def test_check_failure_no_tests_found(self) -> None:
        """Test that check() fails when no tests are found."""
        # Mock subprocess.run to simulate no tests collected
        mock_result = MagicMock()
        mock_result.returncode = 5  # pytest exit code 5 = no tests collected
        mock_result.stdout = (
            "============================= test session starts ==============================\n"
            "collected 0 items\n"
            "============================ no tests ran in 0.01s =============================\n"
        )
        mock_result.stderr = ""

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

            # Verify result
            assert isinstance(result, GateResult)
            assert result.passed is False
            assert result.details["return_code"] == 5

    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, "pytest")
        ):
            gate = TestGate()
            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 pytest is not installed."""
        # Mock subprocess.run to raise FileNotFoundError
        with patch("subprocess.run", side_effect=FileNotFoundError("pytest not found")):
            gate = TestGate()
            result = gate.check()

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

    def test_check_runs_without_coverage(self) -> None:
        """Test that check() runs tests without coverage (coverage is CoverageGate's job)."""
        mock_result = MagicMock()
        mock_result.returncode = 0
        mock_result.stdout = "50 passed in 2.34s"
        mock_result.stderr = ""

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

            # Verify --no-cov flag is present to disable coverage
            call_args = mock_run.call_args[0][0]
            assert isinstance(call_args, list)
            # Should use --no-cov to disable coverage for this gate
            # (coverage is handled by CoverageGate separately)

    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 = "Test failures"
        mock_result.stderr = "Some warnings"

        with patch("subprocess.run", return_value=mock_result):
            gate = TestGate()
            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 = TestGate()
            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 = "50 passed in 2.34s"
        mock_result.stderr = ""

        with patch("subprocess.run", return_value=mock_result) as mock_run:
            gate = TestGate()
            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("pytest", timeout=300),
        ):
            gate = TestGate()
            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
