"""把 Swagger 2 / OpenAPI 3 编译为连接器使用的统一 operation catalog。"""

from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any

HTTP_METHODS = (
    "get",
    "head",
    "post",
    "put",
    "patch",
    "delete",
    "options",
    "trace",
)


def resolve_local_object(spec: dict[str, Any], value: Any) -> Any:
    """解析单个本地 JSON Pointer 引用；远端引用保留原值并由上层拒绝。"""
    if not isinstance(value, dict):
        return value
    ref = value.get("$ref")
    if not isinstance(ref, str) or not ref.startswith("#/"):
        return value
    current: Any = spec
    for raw in ref[2:].split("/"):
        part = raw.replace("~1", "/").replace("~0", "~")
        if not isinstance(current, dict) or part not in current:
            return value
        current = current[part]
    return current if isinstance(current, dict) else value


def operation_id(method: str, path: str, operation: dict[str, Any]) -> str:
    explicit = operation.get("operationId")
    if isinstance(explicit, str) and explicit.strip():
        return explicit.strip()
    safe_path = re.sub(r"[^a-zA-Z0-9]+", "_", path).strip("_")
    return f"{method}_{safe_path}"


@dataclass(frozen=True)
class OperationCatalog:
    operations: tuple[dict[str, Any], ...]

    def find(self, operation_id_value: str) -> tuple[dict[str, Any], ...]:
        return tuple(
            operation
            for operation in self.operations
            if operation["operation_id"] == operation_id_value
        )


def compile_operation_catalog(spec: dict[str, Any]) -> OperationCatalog:
    results: list[dict[str, Any]] = []
    for path, path_item in (spec.get("paths") or {}).items():
        if not isinstance(path, str) or not isinstance(path_item, dict):
            continue
        common = path_item.get("parameters") or []
        for method in HTTP_METHODS:
            operation = path_item.get(method)
            if not isinstance(operation, dict):
                continue
            params = [
                resolve_local_object(spec, param)
                for param in list(common) + list(operation.get("parameters") or [])
            ]
            results.append(
                {
                    "operation_id": operation_id(method, path, operation),
                    "method": method.upper(),
                    "path": path,
                    "summary": operation.get("summary") or "",
                    "description": operation.get("description") or "",
                    "tags": operation.get("tags") or [],
                    "parameters": params,
                    "request_body": resolve_local_object(
                        spec, operation.get("requestBody")
                    ),
                }
            )
    return OperationCatalog(tuple(results))


def validate_json_value(
    spec: dict[str, Any],
    value: Any,
    schema: Any,
    *,
    path: str = "body",
    depth: int = 0,
) -> None:
    """验证调用前最关键的 JSON Schema 子集，复杂语义仍由上游最终判定。"""
    if depth > 20 or not isinstance(schema, dict):
        return
    schema = resolve_local_object(spec, schema)
    expected = schema.get("type")
    type_ok = {
        "object": isinstance(value, dict),
        "array": isinstance(value, list),
        "string": isinstance(value, str),
        "integer": isinstance(value, int) and not isinstance(value, bool),
        "number": isinstance(value, (int, float)) and not isinstance(value, bool),
        "boolean": isinstance(value, bool),
        "null": value is None,
    }
    if expected in type_ok and not type_ok[expected]:
        raise ValueError(f"{path} 应为 {expected}")
    if "enum" in schema and value not in schema.get("enum", []):
        raise ValueError(f"{path} 不在允许值范围内")
    if isinstance(value, (int, float)) and not isinstance(value, bool):
        minimum = schema.get("minimum")
        maximum = schema.get("maximum")
        exclusive_minimum = schema.get("exclusiveMinimum")
        exclusive_maximum = schema.get("exclusiveMaximum")
        if isinstance(minimum, (int, float)):
            if value < minimum or (exclusive_minimum is True and value == minimum):
                operator = ">" if exclusive_minimum is True else ">="
                raise ValueError(f"{path} 必须 {operator} {minimum}")
        if isinstance(maximum, (int, float)):
            if value > maximum or (exclusive_maximum is True and value == maximum):
                operator = "<" if exclusive_maximum is True else "<="
                raise ValueError(f"{path} 必须 {operator} {maximum}")
        if isinstance(exclusive_minimum, (int, float)) and not isinstance(
            exclusive_minimum, bool
        ):
            if value <= exclusive_minimum:
                raise ValueError(f"{path} 必须 > {exclusive_minimum}")
        if isinstance(exclusive_maximum, (int, float)) and not isinstance(
            exclusive_maximum, bool
        ):
            if value >= exclusive_maximum:
                raise ValueError(f"{path} 必须 < {exclusive_maximum}")
    if isinstance(value, str):
        minimum_length = schema.get("minLength")
        maximum_length = schema.get("maxLength")
        if isinstance(minimum_length, int) and len(value) < minimum_length:
            raise ValueError(f"{path} 长度不能小于 {minimum_length}")
        if isinstance(maximum_length, int) and len(value) > maximum_length:
            raise ValueError(f"{path} 长度不能超过 {maximum_length}")
    if isinstance(value, list):
        minimum_items = schema.get("minItems")
        maximum_items = schema.get("maxItems")
        if isinstance(minimum_items, int) and len(value) < minimum_items:
            raise ValueError(f"{path} 项数不能少于 {minimum_items}")
        if isinstance(maximum_items, int) and len(value) > maximum_items:
            raise ValueError(f"{path} 项数不能超过 {maximum_items}")
    if isinstance(value, dict):
        required = schema.get("required") or []
        missing = [str(name) for name in required if name not in value]
        if missing:
            raise ValueError(f"{path} 缺少必填字段: " + ", ".join(missing))
        properties = schema.get("properties") or {}
        if isinstance(properties, dict):
            for name, child in value.items():
                if name in properties:
                    validate_json_value(
                        spec,
                        child,
                        properties[name],
                        path=f"{path}.{name}",
                        depth=depth + 1,
                    )
                elif schema.get("additionalProperties") is False:
                    raise ValueError(f"{path} 包含未定义字段: {name}")
    if isinstance(value, list) and isinstance(schema.get("items"), dict):
        for index, item in enumerate(value):
            validate_json_value(
                spec,
                item,
                schema["items"],
                path=f"{path}[{index}]",
                depth=depth + 1,
            )
