"""A2A Agent Card model and factory.

Provides Pydantic v2 models for the A2A Agent Card specification and a
factory function that builds a card from the facade's supported
operations.  The card is served at ``/.well-known/agent.json`` per the
A2A specification (ADR-047).

The :func:`validate_agent_card_conformance` function validates that a
card satisfies the mandatory fields required by the A2A specification.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict, field_validator

from cleveragents.a2a.models import A2aVersion

if TYPE_CHECKING:
    from cleveragents.a2a.facade import A2aLocalFacade

# ---------------------------------------------------------------------------
# Operation-to-skill mapping
# ---------------------------------------------------------------------------

_SKILL_DEFINITIONS: list[dict[str, str]] = [
    {
        "id": "plan-lifecycle",
        "name": "Plan Lifecycle Management",
        "description": "Create, execute, cancel, and monitor agent plans",
        "prefix": "_cleveragents/plan/",
    },
    {
        "id": "registry-crud",
        "name": "Entity Registry Operations",
        "description": "List and manage tools, resources, actors, and other entities",
        "prefix": "_cleveragents/registry/",
    },
    {
        "id": "context-mgmt",
        "name": "Context Assembly",
        "description": "Inspect, set, and simulate project context",
        "prefix": "_cleveragents/context/",
    },
    {
        "id": "entity-sync",
        "name": "Entity Synchronization",
        "description": "Pull, push, and check sync status across namespaces",
        "prefix": "_cleveragents/sync/",
    },
    {
        "id": "namespace-mgmt",
        "name": "Namespace Management",
        "description": "List, inspect, and manage namespaces and members",
        "prefix": "_cleveragents/namespace/",
    },
    {
        "id": "health-diagnostics",
        "name": "Health and Diagnostics",
        "description": "Health checks and diagnostic reporting",
        "prefix": "_cleveragents/health/",
    },
]


# ---------------------------------------------------------------------------
# Pydantic models
# ---------------------------------------------------------------------------


class AgentCardSkill(BaseModel):
    """A skill advertised in the Agent Card."""

    model_config = ConfigDict(strict=False)

    id: str
    name: str
    description: str = ""

    @field_validator("id", "name")
    @classmethod
    def _must_be_non_empty(cls, value: str) -> str:
        if not value or not value.strip():
            raise ValueError("field must not be empty")
        return value


class AgentCardCapabilities(BaseModel):
    """Capability flags for the Agent Card."""

    model_config = ConfigDict(strict=False)

    streaming: bool = False
    pushNotifications: bool = False


class AgentCardExtension(BaseModel):
    """An extension namespace declaration."""

    model_config = ConfigDict(strict=False)

    uri: str
    description: str = ""

    @field_validator("uri")
    @classmethod
    def _uri_non_empty(cls, value: str) -> str:
        if not value or not value.strip():
            raise ValueError("uri must not be empty")
        return value


class AgentCardSecurityScheme(BaseModel):
    """A security scheme declared by the Agent Card."""

    model_config = ConfigDict(strict=False)

    type: str
    scheme: str = ""

    @field_validator("type")
    @classmethod
    def _type_non_empty(cls, value: str) -> str:
        if not value or not value.strip():
            raise ValueError("type must not be empty")
        return value


class AgentCardInterface(BaseModel):
    """A protocol interface declared by the Agent Card."""

    model_config = ConfigDict(strict=False)

    protocol: str
    url: str = ""

    @field_validator("protocol")
    @classmethod
    def _protocol_non_empty(cls, value: str) -> str:
        if not value or not value.strip():
            raise ValueError("protocol must not be empty")
        return value


class AgentCard(BaseModel):
    """A2A Agent Card describing the agent's capabilities.

    Conforms to the A2A specification Agent Card schema.  All required
    fields (``name``, ``version``, ``url``) are validated on construction.
    """

    model_config = ConfigDict(strict=False)

    name: str
    description: str = ""
    url: str = ""
    version: str = A2aVersion.CURRENT
    capabilities: AgentCardCapabilities = AgentCardCapabilities()
    skills: list[AgentCardSkill] = []
    extensions: list[AgentCardExtension] = []
    securitySchemes: list[AgentCardSecurityScheme] = []
    interfaces: list[AgentCardInterface] = []
    defaultInputModes: list[str] = ["text"]
    defaultOutputModes: list[str] = ["text"]
    supportedVersions: list[str] = list(A2aVersion.SUPPORTED)
    supportedOperations: list[str] = []

    @field_validator("name")
    @classmethod
    def _name_non_empty(cls, value: str) -> str:
        if not value or not value.strip():
            raise ValueError("name must not be empty")
        return value

    @field_validator("version")
    @classmethod
    def _version_non_empty(cls, value: str) -> str:
        if not value or not value.strip():
            raise ValueError("version must not be empty")
        return value


# ---------------------------------------------------------------------------
# Factory
# ---------------------------------------------------------------------------


def build_agent_card(
    *,
    facade: A2aLocalFacade | None = None,
    host: str = "0.0.0.0",
    port: int = 8080,
) -> AgentCard:
    """Build an :class:`AgentCard` from a facade's supported operations.

    Parameters
    ----------
    facade:
        The facade to introspect for supported operations.  When *None*
        a minimal card with no skills is returned.
    host:
        Server bind address used to populate the card URL.
    port:
        Server bind port used to populate the card URL.

    Returns
    -------
    AgentCard
        A fully populated Agent Card.
    """
    if not isinstance(host, str) or not host:
        raise ValueError("host must be a non-empty string")
    if not isinstance(port, int) or port < 1 or port > 65535:
        raise ValueError("port must be an integer between 1 and 65535")

    # Per A2A spec: AgentCard.url is the base server URL (no path suffix).
    # When host is 0.0.0.0 (bind-all), substitute 127.0.0.1 for the card URL
    # since 0.0.0.0 is not a reachable address for clients.
    card_host = "127.0.0.1" if host == "0.0.0.0" else host
    server_url = f"http://{card_host}:{port}"
    endpoint_url = f"http://{card_host}:{port}/a2a"

    operations: list[str] = []
    if facade is not None:
        operations = facade.list_operations()

    # Build skills from operations that match known prefixes
    skills: list[AgentCardSkill] = []
    for defn in _SKILL_DEFINITIONS:
        prefix = defn["prefix"]
        matching = [op for op in operations if op.startswith(prefix)]
        if matching:
            skills.append(
                AgentCardSkill(
                    id=defn["id"],
                    name=defn["name"],
                    description=defn["description"],
                )
            )

    # Also include diagnostics operations in health skill
    diag_ops = [op for op in operations if op.startswith("_cleveragents/diagnostics/")]
    if diag_ops and not any(
        s.id == "health-diagnostics" for s in skills
    ):  # pragma: no cover
        skills.append(
            AgentCardSkill(
                id="health-diagnostics",
                name="Health and Diagnostics",
                description="Health checks and diagnostic reporting",
            )
        )

    # Filter to extension operations only for the supportedOperations list
    extension_ops = [op for op in operations if op.startswith("_cleveragents/")]

    extensions: list[AgentCardExtension] = []
    if extension_ops:
        extensions.append(
            AgentCardExtension(
                uri="urn:cleveragents:extensions:v1",
                description="CleverAgents platform operations",
            )
        )

    return AgentCard(
        name="CleverAgents",
        description="AI-powered development assistant (actor-first)",
        url=server_url,
        version=A2aVersion.CURRENT,
        capabilities=AgentCardCapabilities(
            streaming=False,
            pushNotifications=False,
        ),
        skills=skills,
        extensions=extensions,
        securitySchemes=[
            AgentCardSecurityScheme(type="http", scheme="bearer"),
        ],
        interfaces=[
            AgentCardInterface(protocol="jsonrpc", url=endpoint_url),
        ],
        defaultInputModes=["text"],
        defaultOutputModes=["text"],
        supportedVersions=list(A2aVersion.SUPPORTED),
        supportedOperations=sorted(extension_ops),
    )


# ---------------------------------------------------------------------------
# Conformance validation
# ---------------------------------------------------------------------------


def validate_agent_card_conformance(card: AgentCard) -> list[str]:
    """Validate that *card* conforms to A2A specification requirements.

    Returns a list of violation descriptions.  An empty list means the
    card is conformant.

    Parameters
    ----------
    card:
        The Agent Card to validate.

    Returns
    -------
    list[str]
        Zero or more violation messages.
    """
    if not isinstance(card, AgentCard):
        raise TypeError("card must be an AgentCard instance")

    violations: list[str] = []

    if not card.name or not card.name.strip():
        violations.append("name is required and must not be empty")

    if not card.version or not card.version.strip():
        violations.append("version is required and must not be empty")

    if not card.url or not card.url.strip():
        violations.append("url is required and must not be empty")

    if card.version not in A2aVersion.SUPPORTED:
        violations.append(
            f"version '{card.version}' is not in supported versions "
            f"{list(A2aVersion.SUPPORTED)}"
        )

    if not card.supportedVersions:
        violations.append("supportedVersions must not be empty")

    if not card.defaultInputModes:
        violations.append("defaultInputModes must not be empty")

    if not card.defaultOutputModes:
        violations.append("defaultOutputModes must not be empty")

    # Validate each skill has required fields
    for idx, skill in enumerate(card.skills):
        if not skill.id or not skill.id.strip():
            violations.append(f"skills[{idx}].id must not be empty")
        if not skill.name or not skill.name.strip():
            violations.append(f"skills[{idx}].name must not be empty")

    # Validate each extension has a URI
    for idx, ext in enumerate(card.extensions):
        if not ext.uri or not ext.uri.strip():
            violations.append(f"extensions[{idx}].uri must not be empty")

    return violations


def agent_card_to_dict(card: AgentCard) -> dict[str, Any]:
    """Serialize an :class:`AgentCard` to a JSON-compatible dict.

    Parameters
    ----------
    card:
        The Agent Card to serialize.

    Returns
    -------
    dict[str, Any]
        A dictionary suitable for JSON serialization.
    """
    if not isinstance(card, AgentCard):
        raise TypeError("card must be an AgentCard instance")
    return card.model_dump()


__all__ = [
    "AgentCard",
    "AgentCardCapabilities",
    "AgentCardExtension",
    "AgentCardInterface",
    "AgentCardSecurityScheme",
    "AgentCardSkill",
    "agent_card_to_dict",
    "build_agent_card",
    "validate_agent_card_conformance",
]
