"""Tests for QualityOrchestrator service."""

import asyncio
from unittest.mock import patch

import pytest

from src.gates.quality_gate import GateResult
from src.quality_orchestrator import QualityOrchestrator, VerificationResult


class TestQualityOrchestrator:
    """Test suite for QualityOrchestrator."""

    @pytest.fixture
    def orchestrator(self) -> QualityOrchestrator:
        """Create a QualityOrchestrator instance for testing."""
        return QualityOrchestrator()

    @pytest.mark.asyncio
    async def test_verify_completion_all_gates_pass(
        self, orchestrator: QualityOrchestrator
    ) -> None:
        """Test that verify_completion passes when all gates pass."""
        # Mock all gates to return passing results
        mock_build_result = GateResult(
            passed=True,
            message="Build gate passed: No type errors found",
            details={"return_code": 0},
        )
        mock_lint_result = GateResult(
            passed=True,
            message="Lint gate passed: No linting issues found",
            details={"return_code": 0},
        )
        mock_test_result = GateResult(
            passed=True,
            message="Test gate passed: All tests passed (100% pass rate)",
            details={"return_code": 0},
        )
        mock_coverage_result = GateResult(
            passed=True,
            message="Coverage gate passed: 90.0% coverage (minimum: 85%)",
            details={"coverage_percent": 90.0, "minimum_coverage": 85.0},
        )

        with (
            patch("src.quality_orchestrator.BuildGate") as mock_build_gate,
            patch("src.quality_orchestrator.LintGate") as mock_lint_gate,
            patch("src.quality_orchestrator.TestGate") as mock_test_gate,
            patch("src.quality_orchestrator.CoverageGate") as mock_coverage_gate,
        ):
            # Configure mocks
            mock_build_gate.return_value.check.return_value = mock_build_result
            mock_lint_gate.return_value.check.return_value = mock_lint_result
            mock_test_gate.return_value.check.return_value = mock_test_result
            mock_coverage_gate.return_value.check.return_value = mock_coverage_result

            # Verify completion
            result = await orchestrator.verify_completion()

            # Assert result
            assert isinstance(result, VerificationResult)
            assert result.all_passed is True
            assert len(result.gate_results) == 4
            assert "build" in result.gate_results
            assert "lint" in result.gate_results
            assert "test" in result.gate_results
            assert "coverage" in result.gate_results
            assert result.gate_results["build"].passed is True
            assert result.gate_results["lint"].passed is True
            assert result.gate_results["test"].passed is True
            assert result.gate_results["coverage"].passed is True

    @pytest.mark.asyncio
    async def test_verify_completion_one_gate_fails(
        self, orchestrator: QualityOrchestrator
    ) -> None:
        """Test that verify_completion fails when one gate fails."""
        # Mock gates with one failure
        mock_build_result = GateResult(
            passed=True,
            message="Build gate passed",
            details={},
        )
        mock_lint_result = GateResult(
            passed=False,
            message="Lint gate failed: Linting issues detected",
            details={
                "return_code": 1,
                "stderr": "src/main.py:10: E501 line too long",
            },
        )
        mock_test_result = GateResult(
            passed=True,
            message="Test gate passed",
            details={},
        )
        mock_coverage_result = GateResult(
            passed=True,
            message="Coverage gate passed",
            details={},
        )

        with (
            patch("src.quality_orchestrator.BuildGate") as mock_build_gate,
            patch("src.quality_orchestrator.LintGate") as mock_lint_gate,
            patch("src.quality_orchestrator.TestGate") as mock_test_gate,
            patch("src.quality_orchestrator.CoverageGate") as mock_coverage_gate,
        ):
            # Configure mocks
            mock_build_gate.return_value.check.return_value = mock_build_result
            mock_lint_gate.return_value.check.return_value = mock_lint_result
            mock_test_gate.return_value.check.return_value = mock_test_result
            mock_coverage_gate.return_value.check.return_value = mock_coverage_result

            # Verify completion
            result = await orchestrator.verify_completion()

            # Assert result
            assert isinstance(result, VerificationResult)
            assert result.all_passed is False
            assert result.gate_results["lint"].passed is False
            assert result.gate_results["build"].passed is True
            assert result.gate_results["test"].passed is True
            assert result.gate_results["coverage"].passed is True

    @pytest.mark.asyncio
    async def test_verify_completion_multiple_gates_fail(
        self, orchestrator: QualityOrchestrator
    ) -> None:
        """Test that verify_completion fails when multiple gates fail."""
        # Mock gates with multiple failures
        mock_build_result = GateResult(
            passed=False,
            message="Build gate failed: Type errors detected",
            details={
                "return_code": 1,
                "stderr": "src/main.py:10: error: Incompatible return value type",
            },
        )
        mock_lint_result = GateResult(
            passed=False,
            message="Lint gate failed: Linting issues detected",
            details={
                "return_code": 1,
                "stderr": "src/main.py:10: E501 line too long",
            },
        )
        mock_test_result = GateResult(
            passed=True,
            message="Test gate passed",
            details={},
        )
        mock_coverage_result = GateResult(
            passed=False,
            message="Coverage gate failed: 75.0% coverage below minimum 85%",
            details={"coverage_percent": 75.0, "minimum_coverage": 85.0},
        )

        with (
            patch("src.quality_orchestrator.BuildGate") as mock_build_gate,
            patch("src.quality_orchestrator.LintGate") as mock_lint_gate,
            patch("src.quality_orchestrator.TestGate") as mock_test_gate,
            patch("src.quality_orchestrator.CoverageGate") as mock_coverage_gate,
        ):
            # Configure mocks
            mock_build_gate.return_value.check.return_value = mock_build_result
            mock_lint_gate.return_value.check.return_value = mock_lint_result
            mock_test_gate.return_value.check.return_value = mock_test_result
            mock_coverage_gate.return_value.check.return_value = mock_coverage_result

            # Verify completion
            result = await orchestrator.verify_completion()

            # Assert result
            assert isinstance(result, VerificationResult)
            assert result.all_passed is False
            assert result.gate_results["build"].passed is False
            assert result.gate_results["lint"].passed is False
            assert result.gate_results["test"].passed is True
            assert result.gate_results["coverage"].passed is False

    @pytest.mark.asyncio
    async def test_verify_completion_runs_gates_in_parallel(
        self, orchestrator: QualityOrchestrator
    ) -> None:
        """Test that verify_completion runs all gates in parallel."""
        # Create mock gates with delay to test parallelism
        mock_build_result = GateResult(passed=True, message="Build passed", details={})
        mock_lint_result = GateResult(passed=True, message="Lint passed", details={})
        mock_test_result = GateResult(passed=True, message="Test passed", details={})
        mock_coverage_result = GateResult(
            passed=True, message="Coverage passed", details={}
        )

        # Track call order
        call_order = []

        async def mock_gate_check(gate_name: str, result: GateResult) -> GateResult:
            """Mock gate check with tracking."""
            call_order.append(f"{gate_name}_start")
            await asyncio.sleep(0.01)  # Simulate work
            call_order.append(f"{gate_name}_end")
            return result

        with (
            patch("src.quality_orchestrator.BuildGate") as mock_build_gate,
            patch("src.quality_orchestrator.LintGate") as mock_lint_gate,
            patch("src.quality_orchestrator.TestGate") as mock_test_gate,
            patch("src.quality_orchestrator.CoverageGate") as mock_coverage_gate,
        ):
            # Configure mocks to use async tracking
            mock_build_gate.return_value.check = lambda: mock_gate_check(
                "build", mock_build_result
            )
            mock_lint_gate.return_value.check = lambda: mock_gate_check(
                "lint", mock_lint_result
            )
            mock_test_gate.return_value.check = lambda: mock_gate_check(
                "test", mock_test_result
            )
            mock_coverage_gate.return_value.check = lambda: mock_gate_check(
                "coverage", mock_coverage_result
            )

            # Verify completion
            result = await orchestrator.verify_completion()

            # Assert all gates completed
            assert result.all_passed is True
            assert len(result.gate_results) == 4

            # Assert gates were started before any ended (parallel execution)
            # In parallel execution, all "_start" events should appear before all "_end" events
            start_events = [e for e in call_order if e.endswith("_start")]
            end_events = [e for e in call_order if e.endswith("_end")]

            # All gates should have started
            assert len(start_events) == 4
            # All gates should have ended
            assert len(end_events) == 4

    @pytest.mark.asyncio
    async def test_verify_completion_handles_gate_exception(
        self, orchestrator: QualityOrchestrator
    ) -> None:
        """Test that verify_completion handles exceptions from gates gracefully."""
        # Mock gates with one raising an exception
        mock_build_result = GateResult(passed=True, message="Build passed", details={})

        with (
            patch("src.quality_orchestrator.BuildGate") as mock_build_gate,
            patch("src.quality_orchestrator.LintGate") as mock_lint_gate,
            patch("src.quality_orchestrator.TestGate") as mock_test_gate,
            patch("src.quality_orchestrator.CoverageGate") as mock_coverage_gate,
        ):
            # Configure mocks - one raises exception
            mock_build_gate.return_value.check.return_value = mock_build_result
            mock_lint_gate.return_value.check.side_effect = RuntimeError(
                "Lint gate crashed"
            )
            mock_test_gate.return_value.check.return_value = GateResult(
                passed=True, message="Test passed", details={}
            )
            mock_coverage_gate.return_value.check.return_value = GateResult(
                passed=True, message="Coverage passed", details={}
            )

            # Verify completion
            result = await orchestrator.verify_completion()

            # Assert result - exception should be converted to failure
            assert isinstance(result, VerificationResult)
            assert result.all_passed is False
            assert result.gate_results["lint"].passed is False
            assert "error" in result.gate_results["lint"].message.lower()
            assert result.gate_results["build"].passed is True

    @pytest.mark.asyncio
    async def test_verify_completion_all_gates_fail(
        self, orchestrator: QualityOrchestrator
    ) -> None:
        """Test that verify_completion fails when all gates fail."""
        # Mock all gates to return failing results
        mock_build_result = GateResult(
            passed=False,
            message="Build gate failed",
            details={},
        )
        mock_lint_result = GateResult(
            passed=False,
            message="Lint gate failed",
            details={},
        )
        mock_test_result = GateResult(
            passed=False,
            message="Test gate failed",
            details={},
        )
        mock_coverage_result = GateResult(
            passed=False,
            message="Coverage gate failed",
            details={},
        )

        with (
            patch("src.quality_orchestrator.BuildGate") as mock_build_gate,
            patch("src.quality_orchestrator.LintGate") as mock_lint_gate,
            patch("src.quality_orchestrator.TestGate") as mock_test_gate,
            patch("src.quality_orchestrator.CoverageGate") as mock_coverage_gate,
        ):
            # Configure mocks
            mock_build_gate.return_value.check.return_value = mock_build_result
            mock_lint_gate.return_value.check.return_value = mock_lint_result
            mock_test_gate.return_value.check.return_value = mock_test_result
            mock_coverage_gate.return_value.check.return_value = mock_coverage_result

            # Verify completion
            result = await orchestrator.verify_completion()

            # Assert result
            assert isinstance(result, VerificationResult)
            assert result.all_passed is False
            assert result.gate_results["build"].passed is False
            assert result.gate_results["lint"].passed is False
            assert result.gate_results["test"].passed is False
            assert result.gate_results["coverage"].passed is False
