"""ACT unified verification entry point.

Provides ACTVerifier with verify_mandate (Phase 1) and verify_record
(Phase 2) methods implementing the full verification procedures.

Reference: ACT §8 (Verification Procedure).
"""

from __future__ import annotations

import logging
import time
from typing import Any

from .crypto import ACTKeyResolver, PublicKey, verify as crypto_verify
from .dag import ACTStore, validate_dag
from .delegation import verify_delegation_chain
from .errors import (
    ACTAudienceMismatchError,
    ACTCapabilityError,
    ACTExpiredError,
    ACTPhaseError,
    ACTSignatureError,
    ACTValidationError,
)
from .token import (
    ACTMandate,
    ACTRecord,
    decode_jws,
)

logger = logging.getLogger(__name__)

# Default clock skew tolerance for exp check — ACT §8.1 step 6.
DEFAULT_EXP_CLOCK_SKEW: int = 300  # 5 minutes

# Default clock skew tolerance for iat future check — ACT §8.1 step 7.
DEFAULT_IAT_FUTURE_TOLERANCE: int = 30  # 30 seconds


class ACTVerifier:
    """Unified ACT verification entry point.

    Implements the full verification procedure for both Phase 1
    (Authorization Mandate) and Phase 2 (Execution Record) tokens.

    Reference: ACT §8.
    """

    def __init__(
        self,
        key_resolver: ACTKeyResolver,
        *,
        verifier_id: str | None = None,
        trusted_issuers: set[str] | None = None,
        exp_clock_skew: int = DEFAULT_EXP_CLOCK_SKEW,
        iat_future_tolerance: int = DEFAULT_IAT_FUTURE_TOLERANCE,
        resolve_parent_compact: Any | None = None,
    ) -> None:
        """Initialize the verifier.

        Args:
            key_resolver: Key resolver for all trust tiers.
            verifier_id: This verifier's own identifier (for aud/sub checks).
            trusted_issuers: Set of trusted issuer identifiers.
                             If None, iss check is skipped.
            exp_clock_skew: Maximum clock skew for expiration (seconds).
            iat_future_tolerance: Maximum future iat tolerance (seconds).
            resolve_parent_compact: Callback to resolve parent ACT compact
                                     form by jti (for delegation chain).
        """
        self._key_resolver = key_resolver
        self._verifier_id = verifier_id
        self._trusted_issuers = trusted_issuers
        self._exp_clock_skew = exp_clock_skew
        self._iat_future_tolerance = iat_future_tolerance
        self._resolve_parent_compact = resolve_parent_compact

    def verify_mandate(
        self,
        compact: str,
        *,
        now: int | None = None,
        check_aud: bool = True,
        check_sub: bool = True,
    ) -> ACTMandate:
        """Verify a Phase 1 Authorization Mandate.

        Implements ACT §8.1 verification steps 1-13.

        Args:
            compact: JWS Compact Serialization of the Phase 1 ACT.
            now: Current time override (for testing). Defaults to time.time().
            check_aud: Whether to check aud contains verifier_id.
            check_sub: Whether to check sub matches verifier_id.

        Returns:
            Verified ACTMandate.

        Raises:
            ACTValidationError: Malformed token (steps 2-3, 11).
            ACTSignatureError: Signature failure (step 5).
            ACTExpiredError: Token expired (step 6).
            ACTAudienceMismatchError: Wrong audience (step 8).
            ACTDelegationError: Invalid delegation chain (step 12).
        """
        current_time = now if now is not None else int(time.time())

        # Step 1: Parse JWS Compact Serialization
        header, claims, signature, signing_input = decode_jws(compact)

        # Steps 2-3: typ and alg checked by decode_jws

        # Phase check: must NOT have exec_act
        if "exec_act" in claims:
            raise ACTPhaseError(
                "Token contains exec_act — this is a Phase 2 token, "
                "not a Phase 1 mandate"
            )

        # Step 4: Resolve public key for kid
        kid = header["kid"]
        public_key = self._key_resolver.resolve(kid, header=header)

        # Step 5: Verify JWS signature
        crypto_verify(public_key, signature, signing_input)

        # Build mandate object for claim validation
        mandate = ACTMandate.from_claims(header, claims)

        # Step 6: Check exp not passed
        if current_time > mandate.exp + self._exp_clock_skew:
            raise ACTExpiredError(
                f"Token expired: exp={mandate.exp}, "
                f"now={current_time}, skew={self._exp_clock_skew}"
            )

        # Step 7: Check iat not unreasonably future
        if mandate.iat > current_time + self._iat_future_tolerance:
            raise ACTValidationError(
                f"Token iat is too far in the future: iat={mandate.iat}, "
                f"now={current_time}, tolerance={self._iat_future_tolerance}"
            )

        # Step 8: Check aud contains verifier's identity
        if check_aud and self._verifier_id is not None:
            aud = mandate.aud
            if isinstance(aud, str):
                aud_list = [aud]
            else:
                aud_list = aud
            if self._verifier_id not in aud_list:
                raise ACTAudienceMismatchError(
                    f"Verifier id {self._verifier_id!r} not in aud: {aud_list}"
                )

        # Step 9: Check iss is trusted
        if self._trusted_issuers is not None:
            if mandate.iss not in self._trusted_issuers:
                raise ACTValidationError(
                    f"Issuer {mandate.iss!r} is not trusted"
                )

        # Step 10: Check sub matches verifier's identity
        if check_sub and self._verifier_id is not None:
            if mandate.sub != self._verifier_id:
                raise ACTValidationError(
                    f"sub {mandate.sub!r} does not match verifier id "
                    f"{self._verifier_id!r}"
                )

        # Step 11: Check all required claims (done by from_claims + validate)
        mandate.validate()

        # Step 12: Verify delegation chain
        if mandate.delegation is not None and mandate.delegation.chain:
            def _resolve_key(delegator_id: str) -> PublicKey:
                return self._key_resolver.resolve(delegator_id)

            verify_delegation_chain(
                mandate,
                resolve_key=_resolve_key,
                resolve_parent_compact=self._resolve_parent_compact,
            )

        return mandate

    def verify_record(
        self,
        compact: str,
        store: ACTStore | None = None,
        *,
        now: int | None = None,
        check_aud: bool = True,
    ) -> ACTRecord:
        """Verify a Phase 2 Execution Record.

        Implements all Phase 1 steps (§8.1) plus Phase 2 steps (§8.2).

        Args:
            compact: JWS Compact Serialization of the Phase 2 ACT.
            store: ACT store for DAG validation. If None, DAG checks
                   are limited to capability consistency only.
            now: Current time override (for testing).
            check_aud: Whether to check aud contains verifier_id.

        Returns:
            Verified ACTRecord.

        Raises:
            ACTValidationError: Malformed token.
            ACTSignatureError: Signature failure or wrong signer.
            ACTExpiredError: Token expired.
            ACTAudienceMismatchError: Wrong audience.
            ACTCapabilityError: exec_act not in cap.
            ACTDAGError: DAG validation failure.
        """
        current_time = now if now is not None else int(time.time())

        # Step 1: Parse JWS
        header, claims, signature, signing_input = decode_jws(compact)

        # Phase check
        if "exec_act" not in claims:
            raise ACTPhaseError(
                "Token does not contain exec_act — this is a Phase 1 "
                "mandate, not a Phase 2 record"
            )

        # Step 4: Resolve key — in Phase 2, kid MUST be sub's key
        kid = header["kid"]
        public_key = self._key_resolver.resolve(kid, header=header)

        # Step 5: Verify JWS signature (Step 17: by sub's key)
        crypto_verify(public_key, signature, signing_input)

        # Build record
        record = ACTRecord.from_claims(header, claims)

        # Step 6: Check exp
        if current_time > record.exp + self._exp_clock_skew:
            raise ACTExpiredError(
                f"Token expired: exp={record.exp}, "
                f"now={current_time}, skew={self._exp_clock_skew}"
            )

        # Step 7: iat future check
        if record.iat > current_time + self._iat_future_tolerance:
            raise ACTValidationError(
                f"Token iat is too far in the future: iat={record.iat}"
            )

        # Step 8: aud check
        if check_aud and self._verifier_id is not None:
            aud = record.aud
            if isinstance(aud, str):
                aud_list = [aud]
            else:
                aud_list = aud
            if self._verifier_id not in aud_list:
                raise ACTAudienceMismatchError(
                    f"Verifier id {self._verifier_id!r} not in aud: {aud_list}"
                )

        # Step 9: iss trust check
        if self._trusted_issuers is not None:
            if record.iss not in self._trusted_issuers:
                raise ACTValidationError(
                    f"Issuer {record.iss!r} is not trusted"
                )

        # Step 11: required claims validation
        record.validate()

        # Step 12: delegation chain
        if record.delegation is not None and record.delegation.chain:
            def _resolve_key(delegator_id: str) -> PublicKey:
                return self._key_resolver.resolve(delegator_id)

            # Reuse verify_delegation_chain with ACTRecord fields
            # (it accesses .delegation which exists on ACTRecord too)
            from .delegation import verify_delegation_chain as _vdc
            # Create a temporary mandate-like view — delegation chain
            # verification only needs delegation and cap fields
            mandate_view = ACTMandate(
                alg=record.alg, kid=record.kid,
                iss=record.iss, sub=record.sub, aud=record.aud,
                iat=record.iat, exp=record.exp, jti=record.jti,
                task=record.task, cap=record.cap,
                delegation=record.delegation,
            )
            _vdc(
                mandate_view,
                resolve_key=_resolve_key,
                resolve_parent_compact=self._resolve_parent_compact,
            )

        # Phase 2 step 13: exec_act matches cap[].action
        cap_actions = {c.action for c in record.cap}
        if record.exec_act not in cap_actions:
            raise ACTCapabilityError(
                f"exec_act {record.exec_act!r} does not match any "
                f"cap[].action: {sorted(cap_actions)}"
            )

        # Phase 2 step 14: DAG validation
        if store is not None:
            validate_dag(record, store)

        # Phase 2 step 15: exec_ts checks
        if record.exec_ts < record.iat:
            raise ACTValidationError(
                f"exec_ts {record.exec_ts} is before iat {record.iat}"
            )
        if record.exec_ts > record.exp:
            logger.warning(
                "exec_ts %d is after exp %d — execution after mandate expiry",
                record.exec_ts, record.exp,
            )

        # Phase 2 step 16: status validation (done by record.validate())

        return record
