# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/when-must-an-instance-reject-a-signed-a2a-push-notification-whose-agent-card-advertises-the-correct-jwks-the-stale-kid-rotated-key-boundary-in-a2a-v1.md
# Remnant Warrant:    https://corpusfetch.com/c/when-must-an-instance-reject-a-signed-a2a-push-notification-whose-agent-card-advertises-the-correct-jwks-the-stale-kid-rotated-key-boundary-in-a2a-v1.warrant.json
# Markdown SHA-256:   2d7b399f99e3ecd0dff9230145849a74bd86056217d70c3c27e6cf40a785234a
# Code AST SHA-256:   8c5721103e608b54a3d79ffe2db53fcc596ee0609544e229bc686e4498883c02
# Verified Classes:   WebhookVerificationError, KeyNotFoundError, SignatureInvalidError, TaskStateError, TaskState, JwksCache, TaskRecord
# Defensive Guards:   4 fail-closed invariants (zero external deps)

from __future__ import annotations

import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional


class WebhookVerificationError(RuntimeError):
    """Base for all webhook verification failures."""


class KeyNotFoundError(WebhookVerificationError):
    """JWS kid absent from the current JWKS. Discovery event, NOT a trust verdict."""


class SignatureInvalidError(WebhookVerificationError):
    """Signature was evaluated against a present key and failed. Terminal trust verdict."""


class TaskStateError(WebhookVerificationError):
    """Signature valid but the requested transition is illegal for the current state."""


class TaskState(str, Enum):
    WORKING = "working"
    INPUT_REQUIRED = "input-required"
    COMPLETED = "completed"
    FAILED = "failed"


LEGAL_TRANSITIONS: dict[TaskState, set[TaskState]] = {
    TaskState.WORKING: {TaskState.INPUT_REQUIRED, TaskState.COMPLETED, TaskState.FAILED},
    TaskState.INPUT_REQUIRED: {TaskState.WORKING, TaskState.FAILED},
    TaskState.COMPLETED: set(),
    TaskState.FAILED: set(),
}


@dataclass
class JwksCache:
    keys: dict[str, bytes] = field(default_factory=dict)
    fetched_at: float = 0.0
    ttl_seconds: float = 30.0

    def is_stale(self, now: Optional[float] = None) -> bool:
        now = now if now is not None else time.monotonic()
        return (now - self.fetched_at) > self.ttl_seconds


@dataclass
class TaskRecord:
    task_id: str
    state: TaskState
    failure_reason: Optional[str] = None


def verify_and_apply(
    *,
    task: TaskRecord,
    jws_header_kid: str,
    jws_signature_valid: Optional[bool],  # None => could not evaluate
    jwks: JwksCache,
    refetch_jwks,
    target_state: TaskState,
) -> None:
    """
    Apply a push notification to a task, enforcing the A2A v1.0 boundary:
    key-not-found is a discovery event; only an evaluated-and-failed signature
    is a terminal trust verdict.
    """
    if jws_header_kid not in jwks.keys:
        # Bounded single re-fetch on miss. Do NOT terminate the task here.
        if jwks.is_stale():
            refetch_jwks(jwks)
        if jws_header_kid not in jwks.keys:
            # Still missing after a fresh fetch: unverifiable, not invalid.
            task.state = TaskState.FAILED
            task.failure_reason = "webhook_unverifiable"
            raise KeyNotFoundError(
                f"kid {jws_header_kid!r} absent from JWKS after re-fetch; "
                f"task {task.task_id!r} marked webhook_unverifiable (retryable), "
                f"NOT signature_invalid"
            )
        # Key appeared after re-fetch: fall through to evaluation.

    if jws_signature_valid is False:
        # Evaluated against a present key and failed. This IS terminal.
        task.state = TaskState.FAILED
        task.failure_reason = "signature_invalid"
        raise SignatureInvalidError(
            f"signature for task {task.task_id!r} failed cryptographic verification "
            f"against present kid {jws_header_kid!r}"
        )

    if jws_signature_valid is None:
        # Defensive: should not reach here, but never treat as invalid.
        raise WebhookVerificationError(
            f"signature for task {task.task_id!r} could not be evaluated; "
            f"refusing to transition"
        )

    if target_state not in LEGAL_TRANSITIONS[task.state]:
        raise TaskStateError(
            f"illegal transition {task.state.value} -> {target_state.value} "
            f"for task {task.task_id!r}"
        )

    task.state = target_state
    task.failure_reason = None
