"""Official MCP SDK binding for the single ARR direct-ingestion tool."""

from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any, Dict, Mapping, Tuple

from arr_ingestion.contracts import IngestionError
from arr_mcp.auth import BearerAuthASGI, BearerAuthConfig
from arr_mcp.gateway import DirectResultGateway


TOOL_NAME = "arr_submit_processing_result"
SERVER_VERSION = "1.0.1"
MAX_MCP_REQUEST_BYTES = 4 * 1024 * 1024
PROJECT_ROOT = Path(__file__).resolve().parents[1]
REQUEST_SCHEMA = (
    PROJECT_ROOT
    / "database"
    / "contracts"
    / "arr-submit-processing-result-v1.schema.json"
)
RECEIPT_SCHEMA = (
    PROJECT_ROOT
    / "database"
    / "contracts"
    / "arr-submit-processing-result-receipt-v1.schema.json"
)
STRUCTURED_SCHEMA = (
    PROJECT_ROOT / "database" / "contracts" / "arr-direct-legacy-structured-result-v3.schema.json"
)
LOGGER = logging.getLogger(__name__)


def _json_object(path: Path) -> Dict[str, Any]:
    value = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(value, dict):
        raise ValueError("ARR MCP schema is invalid")
    return value


def _advertised_payload_schema(payload: Mapping[str, Any]) -> Dict[str, Any]:
    """Project the strict payload contract into an LLM-tool-safe Schema.

    The authoritative payload validation stays in ``DirectSubmissionRequest``
    and the independent source replay.  Tool discovery only needs a compact
    shape that tells the Agent to pass every top-level structured-result field.
    Keeping local ``$ref`` values inside a schema nested below ``payload`` is
    not portable: some MCP-to-LLM adapters resolve ``#`` from the outer request
    document and reject the tool before the model can run.
    """

    required = payload.get("required")
    properties = payload.get("properties")
    definitions = payload.get("$defs")
    if (
        not isinstance(required, list)
        or not all(isinstance(value, str) and value for value in required)
        or not isinstance(properties, Mapping)
        or not isinstance(definitions, Mapping)
    ):
        raise ValueError("ARR MCP structured payload schema is invalid")
    required_set = set(required)
    if required_set != set(properties):
        raise ValueError("ARR MCP structured payload fields are invalid")

    sha256 = definitions.get("sha256")
    channel = definitions.get("channel")
    if not isinstance(sha256, Mapping) or not isinstance(channel, Mapping):
        raise ValueError("ARR MCP structured payload definitions are invalid")

    count_names = {
        "source_rows",
        "removed_by_rate_code",
        "removed_as_duplicates",
        "output_rows",
    }
    advertised_properties: Dict[str, Any] = {
        "result_schema_version": {"type": "string", "const": "3.0"},
        "status": {"type": "string", "const": "success"},
        "activation_eligible": {"type": "boolean", "const": True},
        "ingestion_mode": {"type": "string", "const": "opera_xml"},
        "business_date": {"type": "string", "format": "date"},
        "processor_version": {"type": "string", "minLength": 1},
        "rule_set_sha256": dict(sha256),
        "outcome_counts": {
            "type": "object",
            "description": "Complete unmodified outcome_counts object.",
        },
        "channels": {
            "type": "array",
            "items": dict(channel),
            "description": "Complete unmodified channels array.",
        },
        "artifacts": {
            "type": "object",
            "description": "Complete unmodified artifacts object.",
        },
        "records": {
            "type": "array",
            "items": {"type": "object"},
            "description": "Every unmodified structured-result record.",
        },
        "errors": {
            "type": "array",
            "items": {"type": "object"},
            "maxItems": 0,
        },
    }
    for name in count_names:
        advertised_properties[name] = {"type": "integer", "minimum": 0}
    if set(advertised_properties) != required_set:
        raise ValueError("ARR MCP advertised payload fields are invalid")

    return {
        "type": "object",
        "description": (
            "The complete, unmodified successful structured-result.json "
            "object. ARR performs the full contract validation and source replay."
        ),
        "additionalProperties": False,
        "required": list(required),
        "properties": advertised_properties,
    }


def load_tool_schemas() -> Tuple[Dict[str, Any], Dict[str, Any]]:
    """Return a portable advertised request schema and strict receipt."""

    request = _json_object(REQUEST_SCHEMA)
    receipt = _json_object(RECEIPT_SCHEMA)
    payload = _json_object(STRUCTURED_SCHEMA)
    try:
        payload_contract = request["properties"]["payload"]["allOf"]
        reference = payload_contract[0]["$ref"]
    except (KeyError, IndexError, TypeError):
        raise ValueError("ARR MCP request schema is invalid") from None
    expected = (REQUEST_SCHEMA.parent / reference).resolve()
    if expected != STRUCTURED_SCHEMA.resolve():
        raise ValueError("ARR MCP request payload schema is invalid")
    request["properties"]["payload"] = _advertised_payload_schema(payload)
    request.pop("$schema", None)
    request.pop("$id", None)
    return request, receipt


def _json_text(value: Mapping[str, Any]) -> str:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    )


def create_mcp_server(gateway: DirectResultGateway) -> Any:
    """Create the low-level server so the advertised Schema stays exact."""

    import anyio
    import mcp.types as types
    from mcp.server import Server

    request_schema, receipt_schema = load_tool_schemas()
    tool = types.Tool(
        name=TOOL_NAME,
        title="Submit ARR processing result",
        description=(
            "Submit the exact frozen successful ARR structured-result payload "
            "once. ARR independently replays the registered source XML before "
            "atomically activating Finance facts."
        ),
        inputSchema=request_schema,
        outputSchema=receipt_schema,
    )

    async def list_tools(_context: Any, _params: Any) -> Any:
        return types.ListToolsResult(tools=[tool])

    async def call_tool(_context: Any, params: Any) -> Any:
        if params.name != TOOL_NAME:
            error = {
                "code": "MCP_TOOL_NOT_FOUND",
                "message": "requested ARR MCP tool is unavailable",
                "retryable": False,
            }
            return types.CallToolResult(
                content=[types.TextContent(text=_json_text(error))],
                isError=True,
            )
        try:
            result = await anyio.to_thread.run_sync(
                gateway.submit_processing_result,
                params.arguments,
            )
        except IngestionError as error:
            payload = {
                "code": error.code,
                "message": error.safe_message,
                "retryable": error.retryable,
            }
            return types.CallToolResult(
                content=[types.TextContent(text=_json_text(payload))],
                isError=True,
            )
        except Exception as error:
            LOGGER.error(
                "ARR MCP tool failed with unexpected %s",
                type(error).__name__,
            )
            payload = {
                "code": "DIRECT_GATEWAY_INTERNAL",
                "message": "direct result gateway failed safely",
                "retryable": True,
            }
            return types.CallToolResult(
                content=[types.TextContent(text=_json_text(payload))],
                isError=True,
            )
        return types.CallToolResult(
            content=[types.TextContent(text=_json_text(result))],
            structuredContent=result,
        )

    return Server(
        "arr-ingestion-gateway",
        version=SERVER_VERSION,
        title="ARR ingestion gateway",
        description="Attempt-bound validated ARR daily-result ingestion",
        on_list_tools=list_tools,
        on_call_tool=call_tool,
    )


def create_http_application(
    gateway: DirectResultGateway,
    bearer: BearerAuthConfig,
    *,
    allowed_hosts: list[str],
) -> Any:
    if not allowed_hosts or any(not value for value in allowed_hosts):
        raise ValueError("ARR MCP allowed hosts are required")
    from mcp.server.transport_security import TransportSecuritySettings

    server = create_mcp_server(gateway)
    application = server.streamable_http_app(
        json_response=True,
        stateless_http=True,
        max_request_body_size=MAX_MCP_REQUEST_BYTES,
        transport_security=TransportSecuritySettings(
            enable_dns_rebinding_protection=True,
            allowed_hosts=list(allowed_hosts),
            allowed_origins=[],
        ),
    )
    return BearerAuthASGI(application, bearer)
