"""Quality Orchestrator service for coordinating quality gate execution."""

import asyncio
import inspect
from typing import Any, cast
from unittest.mock import Mock

from pydantic import BaseModel, Field

from src.gates.build_gate import BuildGate
from src.gates.coverage_gate import CoverageGate
from src.gates.lint_gate import LintGate
from src.gates.quality_gate import GateResult
from src.gates.test_gate import TestGate


class VerificationResult(BaseModel):
    """Result of quality gate verification.

    Attributes:
        all_passed: Whether all quality gates passed
        gate_results: Dictionary mapping gate names to their results
    """

    all_passed: bool = Field(..., description="Whether all quality gates passed")
    gate_results: dict[str, GateResult] = Field(
        ..., description="Results from each quality gate"
    )


class QualityOrchestrator:
    """Orchestrates execution of all quality gates in parallel.

    The Quality Orchestrator is responsible for:
    - Running all quality gates (build, lint, test, coverage) in parallel
    - Aggregating gate results
    - Determining overall pass/fail status
    """

    def __init__(
        self,
        build_gate: BuildGate | None = None,
        lint_gate: LintGate | None = None,
        test_gate: TestGate | None = None,
        coverage_gate: CoverageGate | None = None,
    ) -> None:
        """Initialize the Quality Orchestrator.

        Args:
            build_gate: Optional BuildGate instance (for testing/DI)
            lint_gate: Optional LintGate instance (for testing/DI)
            test_gate: Optional TestGate instance (for testing/DI)
            coverage_gate: Optional CoverageGate instance (for testing/DI)
        """
        # Use provided gates or create new instances
        # This allows for dependency injection in tests
        self.build_gate = build_gate
        self.lint_gate = lint_gate
        self.test_gate = test_gate
        self.coverage_gate = coverage_gate

    async def verify_completion(self) -> VerificationResult:
        """Verify that all quality gates pass.

        Runs all quality gates in parallel and aggregates the results.

        Returns:
            VerificationResult: Aggregated results from all gates

        Note:
            This method runs all gates in parallel for efficiency.
            Even if one gate fails, all gates will complete execution.
        """
        # Instantiate gates if not provided (lazy initialization)
        # This allows tests to inject mocks, while production uses real gates
        build_gate = self.build_gate if self.build_gate is not None else BuildGate()
        lint_gate = self.lint_gate if self.lint_gate is not None else LintGate()
        test_gate = self.test_gate if self.test_gate is not None else TestGate()
        coverage_gate = self.coverage_gate if self.coverage_gate is not None else CoverageGate()

        # Run all gates in parallel using asyncio.gather
        results = await asyncio.gather(
            self._run_gate_async("build", build_gate),
            self._run_gate_async("lint", lint_gate),
            self._run_gate_async("test", test_gate),
            self._run_gate_async("coverage", coverage_gate),
            return_exceptions=True,  # Capture exceptions instead of raising
        )

        # Build gate results dictionary
        gate_results: dict[str, GateResult] = {}
        gate_names = ["build", "lint", "test", "coverage"]

        for gate_name, result in zip(gate_names, results, strict=True):
            if isinstance(result, Exception):
                # Convert exception to failed GateResult
                gate_results[gate_name] = GateResult(
                    passed=False,
                    message=f"{gate_name.capitalize()} gate failed: Unexpected error: {result}",
                    details={"error": str(result), "exception_type": type(result).__name__},
                )
            elif isinstance(result, GateResult):
                gate_results[gate_name] = result
            else:
                # Unexpected type - treat as error
                gate_results[gate_name] = GateResult(
                    passed=False,
                    message=f"{gate_name.capitalize()} gate failed: Unexpected result type",
                    details={"error": f"Expected GateResult, got {type(result).__name__}"},
                )

        # Determine if all gates passed
        all_passed = all(result.passed for result in gate_results.values())

        return VerificationResult(all_passed=all_passed, gate_results=gate_results)

    async def _run_gate_async(self, gate_name: str, gate: Any) -> GateResult:
        """Run a gate check asynchronously.

        Args:
            gate_name: Name of the gate for error reporting
            gate: Gate instance to execute

        Returns:
            GateResult: Result from the gate check

        Note:
            This method handles both synchronous gates (production) and async mocks (testing).
            Production gates are run in a thread pool to avoid blocking the event loop.
            Test mocks can be async functions or lambdas returning coroutines.
        """
        # Check if gate.check is an async function
        if inspect.iscoroutinefunction(gate.check):
            return cast(GateResult, await gate.check())

        # Check if it's a real production gate instance
        if self._is_real_gate(gate):
            # Real gate - run in thread pool to avoid blocking event loop
            return cast(GateResult, await asyncio.to_thread(gate.check))

        # Handle test mocks and callables
        return await self._handle_test_mock(gate)

    def _is_real_gate(self, gate: Any) -> bool:
        """Check if gate is a real production gate instance.

        Args:
            gate: Gate instance to check

        Returns:
            bool: True if gate is a real production gate
        """
        if not inspect.ismethod(gate.check):
            return False

        gate_class_name = gate.__class__.__name__
        return gate_class_name in ("BuildGate", "LintGate", "TestGate", "CoverageGate")

    async def _handle_test_mock(self, gate: Any) -> GateResult:
        """Handle test mocks and callables.

        Args:
            gate: Gate mock or callable to handle

        Returns:
            GateResult: Result from the mock
        """
        # Check if it's a Mock/MagicMock (testing scenario)
        mock_types = ("Mock", "MagicMock", "AsyncMock")
        if isinstance(gate.check, Mock) or type(gate.check).__name__ in mock_types:
            result_or_coro = gate.check()
            if asyncio.iscoroutine(result_or_coro):
                return cast(GateResult, await result_or_coro)
            return cast(GateResult, result_or_coro)

        # For any other callable (lambdas, functions), call and check result
        result_or_coro = gate.check()
        if asyncio.iscoroutine(result_or_coro):
            return cast(GateResult, await result_or_coro)
        return cast(GateResult, result_or_coro)
