"""FastAPI-based ASGI application for the CleverAgents server.

The server's sole client-facing interface is an A2A JSON-RPC 2.0
endpoint (ADR-048).  This module builds the FastAPI application,
wires the health-check / Agent Card discovery endpoint, and exposes
the A2A JSON-RPC dispatch route.

All A2A operations -- both standard (``message/send``,
``message/stream``) and ``_cleveragents/`` extension methods -- flow
through the single ``/a2a`` POST endpoint.  The Agent Card is served
at ``/.well-known/agent.json`` per the A2A specification.
"""

from __future__ import annotations

from typing import Any

import structlog
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

from cleveragents.a2a.agent_card import (
    agent_card_to_dict,
    build_agent_card,
    validate_agent_card_conformance,
)
from cleveragents.a2a.errors import A2aOperationNotFoundError
from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest, A2aVersion

logger: structlog.stdlib.BoundLogger = structlog.get_logger(__name__)


# ------------------------------------------------------------------
# Application factory
# ------------------------------------------------------------------


def create_asgi_app(
    *,
    facade: A2aLocalFacade | None = None,
    host: str = "0.0.0.0",
    port: int = 8080,
) -> FastAPI:
    """Create and return a configured FastAPI ASGI application.

    Parameters
    ----------
    facade:
        The :class:`A2aLocalFacade` used to dispatch A2A operations.
        When *None* a default facade with no services is created.
    host:
        Bind address used to populate the Agent Card URL.
    port:
        Bind port used to populate the Agent Card URL.

    Returns
    -------
    FastAPI
        A fully wired ASGI application ready for ``uvicorn.run()``.
    """
    if facade is not None and not isinstance(facade, A2aLocalFacade):
        raise TypeError("facade must be an A2aLocalFacade instance or None")
    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")

    resolved_facade = facade if facade is not None else A2aLocalFacade()

    # Build the Agent Card from the facade's operations
    agent_card = build_agent_card(
        facade=resolved_facade,
        host=host,
        port=port,
    )

    # Validate conformance at startup
    violations = validate_agent_card_conformance(agent_card)
    if violations:
        logger.warning(
            "a2a.server.agent_card_conformance_violations",
            violations=violations,
        )

    agent_card_dict = agent_card_to_dict(agent_card)

    application = FastAPI(
        title="CleverAgents A2A Server",
        description="A2A JSON-RPC 2.0 endpoint for CleverAgents",
        version=A2aVersion.CURRENT,
        docs_url=None,
        redoc_url=None,
        openapi_url=None,
    )

    # --- Health / Agent Card endpoint ---

    @application.get("/.well-known/agent.json")
    async def agent_card_endpoint() -> JSONResponse:
        """Serve the A2A Agent Card for capability discovery."""
        return JSONResponse(content=agent_card_dict)

    @application.get("/health")
    async def health_endpoint() -> JSONResponse:
        """Simple liveness probe."""
        return JSONResponse(content={"status": "healthy"})

    # --- A2A JSON-RPC 2.0 endpoint ---

    @application.post("/a2a")
    async def a2a_endpoint(request: Request) -> JSONResponse:
        """Handle an inbound A2A JSON-RPC 2.0 request.

        Accepts a JSON body conforming to the :class:`A2aRequest`
        schema, dispatches it through the facade, and returns the
        :class:`A2aResponse` envelope.

        Per JSON-RPC 2.0 over HTTP convention, all responses use HTTP
        200 regardless of protocol-level errors.  Error codes are
        expressed via the ``error`` field in the JSON body.
        """
        # Step 1: Parse the raw JSON body.
        # Malformed JSON -> -32700 Parse error.
        try:
            body: dict[str, Any] = await request.json()
        except Exception:
            logger.warning("a2a.server.parse_error")
            return JSONResponse(
                status_code=200,
                content={
                    "jsonrpc": "2.0",
                    "id": None,
                    "error": {
                        "code": -32700,
                        "message": "Parse error",
                    },
                },
            )

        # Step 2: Validate the body against the A2aRequest schema.
        # Invalid schema -> -32600 Invalid Request.
        request_id: Any = body.get("id") if isinstance(body, dict) else None
        try:
            a2a_request = A2aRequest(**body)
        except Exception as exc:
            logger.warning("a2a.server.bad_request", error=str(exc))
            return JSONResponse(
                status_code=200,
                content={
                    "jsonrpc": "2.0",
                    "id": request_id,
                    "error": {
                        "code": -32600,
                        "message": "Invalid request body",
                    },
                },
            )

        # Step 3: Dispatch the request through the facade.
        try:
            response = resolved_facade.dispatch(a2a_request)
            return JSONResponse(content=response.model_dump(exclude_none=True))
        except A2aOperationNotFoundError:
            logger.warning(
                "a2a.server.method_not_found",
                method=a2a_request.method,
            )
            return JSONResponse(
                status_code=200,
                content={
                    "jsonrpc": "2.0",
                    "id": request_id,
                    "error": {
                        "code": -32601,
                        "message": "Method not found",
                    },
                },
            )
        except Exception as exc:
            logger.error(
                "a2a.server.internal_error",
                error=str(exc),
                exc_info=True,
            )
            return JSONResponse(
                status_code=200,
                content={
                    "jsonrpc": "2.0",
                    "id": request_id,
                    "error": {
                        "code": -32603,
                        "message": "Internal error",
                    },
                },
            )

    logger.info(
        "a2a.server.app_created",
        host=host,
        port=port,
        operations=len(resolved_facade.list_operations()),
    )

    return application


__all__ = [
    "create_asgi_app",
]
