# CorpusFetch Method Slice (Zero-Prose Python Stdlib Reference)
# Canonical Residual: https://corpusfetch.com/c/in-what-scenarios-must-an-instance-reject-a-peer-s-a2a-agent-card-despite-successful-schema-validation-the-capability-declaration-transport-binding-de.md
# Remnant Warrant:    https://corpusfetch.com/c/in-what-scenarios-must-an-instance-reject-a-peer-s-a2a-agent-card-despite-successful-schema-validation-the-capability-declaration-transport-binding-de.warrant.json
# Markdown SHA-256:   5b77813345d992f2df43835b72f8893961be2357352188a2ff13c2a984f5dfc6
# Code AST SHA-256:   689187e94c131e925fb79878c045c2fa168ffa1e05bc8e028069fc3ba7118638
# Verified Classes:   BindingCapabilityMismatch, Binding, AgentCard
# Defensive Guards:   6 fail-closed invariants (zero external deps)

from __future__ import annotations

from dataclasses import dataclass
from typing import Iterable


class BindingCapabilityMismatch(RuntimeError):
    """Raised when a schema-valid A2A card's declared capabilities are not
    closed under the transport binding the delegator will actually invoke."""


@dataclass(frozen=True)
class Binding:
    name: str
    supports_streaming: bool
    supports_push: bool


@dataclass(frozen=True)
class AgentCard:
    protocol_version: str
    capabilities: dict[str, bool]
    default_input_modes: tuple[str, ...]
    additional_interfaces: tuple[Binding, ...]
    skills: dict[str, tuple[str, ...]]


def select_binding(card: AgentCard, preferred: str) -> Binding:
    for b in card.additional_interfaces:
        if b.name == preferred:
            return b
    raise BindingCapabilityMismatch(
        f"preferred binding {preferred!r} not advertised by card"
    )


def assert_card_usable(
    card: AgentCard,
    preferred_binding: str,
    intended_mime: str,
    skill_id: str | None = None,
) -> Binding:
    if card.protocol_version.split(".")[0] != "1":
        raise BindingCapabilityMismatch(
            f"unsupported protocolVersion {card.protocol_version!r}"
        )

    binding = select_binding(card, preferred_binding)

    if card.capabilities.get("streaming") and not binding.supports_streaming:
        raise BindingCapabilityMismatch(
            f"card declares streaming=true but binding {binding.name!r} "
            "does not implement SendStreamingMessage"
        )

    if card.capabilities.get("pushNotifications") and not binding.supports_push:
        raise BindingCapabilityMismatch(
            f"card declares pushNotifications=true but binding {binding.name!r} "
            "exposes no push endpoint"
        )

    allowed = set(card.default_input_modes)
    if skill_id is not None:
        if skill_id not in card.skills:
            raise BindingCapabilityMismatch(f"unknown skill {skill_id!r}")
        allowed |= set(card.skills[skill_id])

    if intended_mime not in allowed:
        raise BindingCapabilityMismatch(
            f"intended input mode {intended_mime!r} not declared for "
            f"skill={skill_id!r} on binding {binding.name!r}"
        )

    return binding
