"""Passport validation and access decision utilities."""

from __future__ import annotations

import time
from typing import Any

import jwt

from ai4gh.core.exceptions import AuthError
from ai4gh.specs.passports.models import (
    AccessDecision,
    PassportClaims,
    VisaClaim,
    VisaType,
)


class PassportValidator:
    """Validates GA4GH Passport JWTs and makes access decisions.

    This provides higher-level validation than core.auth.PassportAuth,
    including structured Visa extraction, expiration checking, and
    access decision evaluation against required visa criteria.
    """

    def __init__(
        self,
        *,
        algorithms: list[str] | None = None,
    ) -> None:
        self._algorithms = algorithms or ["RS256", "ES256"]

    def decode(
        self,
        token: str,
        *,
        verify: bool = False,
        key: str = "",
    ) -> PassportClaims:
        """Decode a Passport JWT into structured claims.

        Args:
            token: The JWT string.
            verify: Whether to verify the signature.
            key: Public key for verification (required if verify=True).

        Returns:
            Structured passport claims.
        """
        options: dict[str, Any] = {"verify_signature": verify}
        if not verify:
            options["verify_exp"] = False
            options["verify_aud"] = False

        try:
            payload = jwt.decode(
                token,
                key=key,
                algorithms=self._algorithms,
                options=options,  # type: ignore[arg-type]
            )
        except jwt.InvalidTokenError as e:
            raise AuthError(f"Invalid Passport token: {e}") from e

        return PassportClaims.model_validate(payload)

    def extract_visas(
        self,
        claims: PassportClaims,
        *,
        visa_type: VisaType | None = None,
        source: str | None = None,
    ) -> list[VisaClaim]:
        """Extract visas from passport claims with optional filtering.

        Args:
            claims: Decoded passport claims.
            visa_type: Filter by visa type.
            source: Filter by visa source.

        Returns:
            List of matching visa claims.
        """
        visas = claims.ga4gh_passport_v1 or []
        if visa_type is not None:
            visas = [v for v in visas if v.type == visa_type]
        if source is not None:
            visas = [v for v in visas if v.source == source]
        return visas

    def is_expired(self, claims: PassportClaims) -> bool:
        """Check if the passport has expired.

        Args:
            claims: Decoded passport claims.

        Returns:
            True if expired, False otherwise.
        """
        if claims.exp is None:
            return False
        return time.time() > claims.exp

    def evaluate_access(
        self,
        claims: PassportClaims,
        *,
        required_type: VisaType,
        required_value: str | None = None,
        required_source: str | None = None,
    ) -> AccessDecision:
        """Evaluate whether a passport grants access based on criteria.

        Args:
            claims: Decoded passport claims.
            required_type: The visa type required for access.
            required_value: Required visa value pattern.
            required_source: Required visa source.

        Returns:
            An access decision with matching visas.
        """
        if self.is_expired(claims):
            return AccessDecision(
                granted=False,
                reason="Passport has expired",
            )

        matching = self.extract_visas(claims, visa_type=required_type, source=required_source)

        if required_value is not None:
            matching = [v for v in matching if v.value == required_value]

        if matching:
            return AccessDecision(
                granted=True,
                reason=f"Found {len(matching)} matching visa(s)",
                matching_visas=matching,
            )

        return AccessDecision(
            granted=False,
            reason=f"No visa of type {required_type.value} found",
        )
