"""Async GuardrailPipeline — concurrent evaluation for I/O-bound envs."""

from __future__ import annotations

import asyncio
import inspect
import time
from typing import Any, Dict, List, Optional, Union

from briefcase.guardrails.framework import (
    AsyncGuardrailEnv,
    Effect,
    EvalRequest,
    EvalResult,
    GuardrailEnv,
    PipelineMode,
    PipelineResult,
    SyncAdapter,
)


def _is_async_env(env: Any) -> bool:
    """Check if env has async evaluate (runtime_checkable can't distinguish)."""
    evaluate = getattr(env, "evaluate", None)
    return evaluate is not None and asyncio.iscoroutinefunction(evaluate)


class AsyncGuardrailPipeline:
    """Async version of GuardrailPipeline with concurrent evaluation.

    Accepts both sync and async envs. Sync envs are auto-wrapped
    with SyncAdapter.

    Modes:
    - FIRST_DENY: sequential, short-circuits on first DENY
    - ALL: concurrent via asyncio.gather, DENY if any deny
    - MAJORITY: concurrent via asyncio.gather, majority vote

    Error handling (on_error):
    - "deny" (default): exception → DENY EvalResult with metadata["error"]
    - "propagate": re-raise the exception
    - "skip": skip erroring stage, continue with remaining
    """

    def __init__(
        self,
        stages: List[Union[GuardrailEnv, AsyncGuardrailEnv]],
        mode: PipelineMode = PipelineMode.FIRST_DENY,
        name: str = "async-pipeline",
        on_error: str = "deny",
    ):
        if on_error not in ("deny", "propagate", "skip"):
            raise ValueError(f"on_error must be 'deny', 'propagate', or 'skip', got '{on_error}'")
        self._stages: List[AsyncGuardrailEnv] = []
        for s in stages:
            if _is_async_env(s):
                self._stages.append(s)
            else:
                self._stages.append(SyncAdapter(s))
        self._mode = mode
        self._name = name
        self._on_error = on_error

    @property
    def name(self) -> str:
        return self._name

    @property
    def stages(self) -> List[AsyncGuardrailEnv]:
        return list(self._stages)

    def _make_error_result(self, stage, exc: Exception) -> EvalResult:
        """Create a DENY EvalResult from an exception."""
        return EvalResult(
            effect=Effect.DENY,
            guardrail_name=getattr(stage, "name", "unknown"),
            reason=f"Error: {type(exc).__name__}: {exc}",
            metadata={"error": True, "exception": str(exc)},
        )

    async def evaluate(self, request: EvalRequest) -> PipelineResult:
        """Evaluate request through all pipeline stages."""
        start = time.monotonic()

        if self._mode == PipelineMode.FIRST_DENY:
            return await self._evaluate_first_deny(request, start)

        # ALL and MAJORITY: concurrent evaluation
        if self._on_error == "propagate":
            tasks = [stage.evaluate(request) for stage in self._stages]
            results: List[EvalResult] = await asyncio.gather(*tasks)
        else:
            tasks = [stage.evaluate(request) for stage in self._stages]
            raw_results = await asyncio.gather(*tasks, return_exceptions=True)
            results = []
            for i, r in enumerate(raw_results):
                if isinstance(r, Exception):
                    if self._on_error == "skip":
                        continue
                    # "deny"
                    results.append(self._make_error_result(self._stages[i], r))
                else:
                    results.append(r)

        elapsed = (time.monotonic() - start) * 1000.0

        if not results:
            # All stages were skipped
            return PipelineResult(
                final_effect=Effect.ALLOW,
                individual_results=[],
                short_circuited=False,
                eval_time_ms=elapsed,
            )

        if self._mode == PipelineMode.MAJORITY:
            allow_count = sum(1 for r in results if r.effect == Effect.ALLOW)
            final = Effect.ALLOW if allow_count > len(results) / 2 else Effect.DENY
        else:
            # ALL mode
            has_deny = any(r.effect == Effect.DENY for r in results)
            final = Effect.DENY if has_deny else Effect.ALLOW

        return PipelineResult(
            final_effect=final,
            individual_results=results,
            short_circuited=False,
            eval_time_ms=elapsed,
        )

    async def _evaluate_first_deny(
        self, request: EvalRequest, start: float
    ) -> PipelineResult:
        """Sequential evaluation, short-circuit on first DENY."""
        results: List[EvalResult] = []
        for stage in self._stages:
            try:
                result = await stage.evaluate(request)
            except Exception as exc:
                if self._on_error == "propagate":
                    raise
                if self._on_error == "skip":
                    continue
                # "deny"
                result = self._make_error_result(stage, exc)
            results.append(result)
            if result.effect == Effect.DENY:
                elapsed = (time.monotonic() - start) * 1000.0
                return PipelineResult(
                    final_effect=Effect.DENY,
                    individual_results=results,
                    short_circuited=True,
                    eval_time_ms=elapsed,
                )

        elapsed = (time.monotonic() - start) * 1000.0
        return PipelineResult(
            final_effect=Effect.ALLOW,
            individual_results=results,
            short_circuited=False,
            eval_time_ms=elapsed,
        )
