from __future__ import annotations

from typing import Any

from jsonschema import ValidationError, validate
from pydantic import ValidationError as PydanticValidationError

from .mcp_errors import MCPToolError
from .registry import Operation
from .schema_registry import get_validator


SKIP_RESPONSE_VALIDATION = {
    "overview.list",
    "whoami.list",
    "users.list",
    "queues.list",
    "exchanges.list",
    "connections.list",
    "vhosts.list",
}


def _schema_has_ref(schema: dict[str, Any]) -> bool:
    if "$ref" in schema:
        return True
    items = schema.get("items")
    if isinstance(items, dict) and "$ref" in items:
        return True
    return False


def _schema_for_request_body(operation: Operation) -> dict[str, Any] | None:
    if not operation.request_body:
        return None
    content = operation.request_body.get("content", {})
    if isinstance(content, dict) and "application/json" in content:
        schema = content.get("application/json", {}).get("schema")
        if isinstance(schema, dict):
            return schema
    return None


def validate_call(
    operation: Operation,
    path_params: dict[str, Any] | None,
    query_params: dict[str, Any] | None,
    body: dict[str, Any] | None,
) -> None:
    path_params = path_params or {}
    query_params = query_params or {}

    for param in operation.parameters:
        if not param.required:
            continue
        if param.location == "path":
            placeholder = f"{{{param.name}}}"
            if placeholder not in operation.path:
                continue
            if param.name not in path_params:
                raise MCPToolError(
                    code="MISSING_PATH_PARAM",
                    message=f"Missing path parameter: {param.name}",
                )
            continue
            raise MCPToolError(
                code="MISSING_PATH_PARAM",
                message=f"Missing path parameter: {param.name}",
            )
        if param.location == "query" and param.name not in query_params:
            raise MCPToolError(
                code="MISSING_QUERY_PARAM",
                message=f"Missing query parameter: {param.name}",
            )

    required_body = bool(operation.request_body and operation.request_body.get("required"))
    if required_body and body is None:
        raise MCPToolError(code="MISSING_BODY", message="Request body is required")

    if body is not None:
        schema = _schema_for_request_body(operation)
        if schema:
            try:
                try:
                    validator = get_validator(schema)
                    validator(body)
                except LookupError:
                    validate(instance=body, schema=schema)
            except (ValidationError, PydanticValidationError) as exc:
                raise MCPToolError(
                    code="INVALID_BODY",
                    message="Request body validation failed",
                    details={"error": str(exc)},
                ) from exc


def _select_response_schema(operation: Operation, status_code: int) -> dict[str, Any] | None:
    if not operation.responses:
        return None
    status_key = str(status_code)
    if status_key in operation.responses:
        return operation.responses[status_key]
    if 200 <= status_code < 300:
        for key, schema in operation.responses.items():
            if key.startswith("2"):
                return schema
    return None


def validate_response(operation: Operation, status_code: int, payload: Any) -> None:
    if payload is None:
        return
    if operation.operation_id in SKIP_RESPONSE_VALIDATION:
        return
    schema = _select_response_schema(operation, status_code)
    if not schema:
        return
    try:
        try:
            validator = get_validator(schema)
            validator(payload)
        except LookupError:
            if _schema_has_ref(schema):
                return
            validate(instance=payload, schema=schema)
    except (ValidationError, PydanticValidationError) as exc:
        raise MCPToolError(
            code="INVALID_RESPONSE",
            message="Response validation failed",
            details={"error": str(exc)},
        ) from exc
