from __future__ import annotations

import argparse
import json
from pathlib import Path
from typing import Any

import yaml

HTTP_METHODS = {"get", "post", "put", "delete", "patch", "head", "options"}
METHOD_OVERRIDES = {
    "exchanges_publish.update_by_params": "post",
}
PATH_OVERRIDES = {
    "vhosts.update": "/api/vhosts/{name}",
    "vhosts.delete": "/api/vhosts/{name}",
}


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Generate operations registry")
    parser.add_argument(
        "--spec-path",
        default="src/rabbitmq_mcp/data/rabbitmq-http-api-openapi.yaml",
    )
    parser.add_argument(
        "--output-path",
        default="src/rabbitmq_mcp/data/operations.json",
    )
    parser.add_argument("--no-include-amqp", action="store_true")
    return parser.parse_args()


def load_spec(path: Path) -> dict[str, Any]:
    return yaml.safe_load(path.read_text(encoding="utf-8"))


def resolve_ref(spec: dict[str, Any], ref: str) -> dict[str, Any]:
    current: Any = spec
    for part in ref.lstrip("#/").split("/"):
        if not isinstance(current, dict) or part not in current:
            return {"$ref": ref}
        current = current[part]
    if not isinstance(current, dict):
        return {"$ref": ref}
    return dict(current)


def resolve_schema(spec: dict[str, Any], schema: dict[str, Any]) -> dict[str, Any]:
    if "$ref" in schema:
        return resolve_ref(spec, schema["$ref"])
    return schema


def resolve_parameter(spec: dict[str, Any], raw: dict[str, Any]) -> dict[str, Any]:
    if "$ref" in raw:
        return resolve_ref(spec, raw["$ref"])
    if "schema" in raw and isinstance(raw["schema"], dict):
        raw = dict(raw)
        raw["schema"] = resolve_schema(spec, raw["schema"])
    return raw


def resolve_request_body(spec: dict[str, Any], raw: dict[str, Any]) -> dict[str, Any]:
    if "$ref" in raw:
        return resolve_ref(spec, raw["$ref"])
    content = raw.get("content")
    if isinstance(content, dict) and "application/json" in content:
        content = dict(content)
        schema = content.get("application/json", {}).get("schema")
        if isinstance(schema, dict):
            content["application/json"] = dict(content["application/json"])
            content["application/json"]["schema"] = resolve_schema(spec, schema)
        raw = dict(raw)
        raw["content"] = content
    return raw


def resolve_responses(spec: dict[str, Any], responses: dict[str, Any]) -> dict[str, Any]:
    resolved: dict[str, Any] = {}
    for status, response in responses.items():
        if not isinstance(response, dict):
            continue
        content = response.get("content")
        if not isinstance(content, dict):
            continue
        app_json = content.get("application/json")
        if not isinstance(app_json, dict):
            continue
        schema = app_json.get("schema")
        if isinstance(schema, dict):
            resolved[status] = resolve_schema(spec, schema)
    return resolved


def is_destructive(operation_id: str, method: str, path: str) -> bool:
    destructive_tokens = ["delete", "purge", "reset", "close", "shutdown"]
    if method.lower() == "delete":
        return True
    lowered = operation_id.lower()
    if any(token in lowered for token in destructive_tokens):
        return True
    if path.endswith("/contents") and method.lower() == "delete":
        return True
    return False


def namespace_for(operation_id: str) -> str:
    if "." in operation_id:
        return operation_id.split(".", 1)[0]
    return operation_id


def build_operations(spec: dict[str, Any]) -> dict[str, Any]:
    operations: dict[str, Any] = {}
    for path, item in spec.get("paths", {}).items():
        if not isinstance(item, dict):
            continue
        path_params = [resolve_parameter(spec, p) for p in item.get("parameters", [])]
        for method, operation in item.items():
            if method not in HTTP_METHODS or not isinstance(operation, dict):
                continue
            operation_id = operation.get("operationId")
            if not operation_id:
                continue
            normalized_path = PATH_OVERRIDES.get(operation_id, path)
            op_params = [resolve_parameter(spec, p) for p in operation.get("parameters", [])]
            parameters = path_params + op_params
            filtered_params = []
            for param in parameters:
                if param.get("in") == "path":
                    placeholder = f"{{{param.get('name')}}}"
                    if placeholder not in normalized_path:
                        continue
                filtered_params.append(param)
            request_body = operation.get("requestBody")
            if request_body:
                request_body = resolve_request_body(spec, request_body)
            responses = resolve_responses(spec, operation.get("responses", {}))
            normalized_method = METHOD_OVERRIDES.get(operation_id, method)
            operations[operation_id] = {
                "operation_id": operation_id,
                "namespace": namespace_for(operation_id),
                "protocol": "http",
                "method": normalized_method,
                "path": normalized_path,
                "summary": operation.get("summary"),
                "description": operation.get("description"),
                "deprecated": bool(operation.get("deprecated", False)),
                "safety_validation_required": is_destructive(
                    operation_id, normalized_method, normalized_path
                ),
                "parameters": [
                    {
                        "name": param.get("name"),
                        "in": param.get("in"),
                        "required": bool(param.get("required", False)),
                        "schema": param.get("schema"),
                    }
                    for param in filtered_params
                ],
                "request_body": request_body,
                "responses": responses or None,
            }
    return operations


def amqp_operations() -> dict[str, Any]:
    return {
        "amqp.publish": {
            "operation_id": "amqp.publish",
            "namespace": "amqp",
            "protocol": "amqp",
            "method": "",
            "path": "",
            "summary": "Publish a message via AMQP",
            "description": "Publish a message to an exchange using AMQP",
            "deprecated": False,
            "safety_validation_required": False,
            "parameters": [
                {"name": "exchange", "in": "body", "required": True, "schema": {"type": "string"}},
                {"name": "routing_key", "in": "body", "required": True, "schema": {"type": "string"}},
                {"name": "payload", "in": "body", "required": True, "schema": {"type": "string"}},
                {"name": "payload_encoding", "in": "body", "required": False, "schema": {"type": "string"}},
                {"name": "properties", "in": "body", "required": False, "schema": {"type": "object"}},
            ],
            "request_body": None,
        },
        "amqp.consume": {
            "operation_id": "amqp.consume",
            "namespace": "amqp",
            "protocol": "amqp",
            "method": "",
            "path": "",
            "summary": "Consume messages via AMQP",
            "description": "Consume messages from a queue",
            "deprecated": False,
            "safety_validation_required": False,
            "parameters": [
                {"name": "queue", "in": "body", "required": True, "schema": {"type": "string"}},
                {"name": "max_messages", "in": "body", "required": False, "schema": {"type": "integer"}},
                {"name": "auto_ack", "in": "body", "required": False, "schema": {"type": "boolean"}},
            ],
            "request_body": None,
        },
        "amqp.ack": {
            "operation_id": "amqp.ack",
            "namespace": "amqp",
            "protocol": "amqp",
            "method": "",
            "path": "",
            "summary": "Acknowledge a message",
            "description": "Acknowledge a consumed message using receipt_handle",
            "deprecated": False,
            "safety_validation_required": False,
            "parameters": [
                {
                    "name": "receipt_handle",
                    "in": "body",
                    "required": True,
                    "schema": {"type": "string"},
                }
            ],
            "request_body": None,
        },
        "amqp.nack": {
            "operation_id": "amqp.nack",
            "namespace": "amqp",
            "protocol": "amqp",
            "method": "",
            "path": "",
            "summary": "Negative acknowledge a message",
            "description": "Nack a consumed message using receipt_handle",
            "deprecated": False,
            "safety_validation_required": False,
            "parameters": [
                {
                    "name": "receipt_handle",
                    "in": "body",
                    "required": True,
                    "schema": {"type": "string"},
                },
                {
                    "name": "requeue",
                    "in": "body",
                    "required": False,
                    "schema": {"type": "boolean"},
                },
            ],
            "request_body": None,
        },
        "amqp.reject": {
            "operation_id": "amqp.reject",
            "namespace": "amqp",
            "protocol": "amqp",
            "method": "",
            "path": "",
            "summary": "Reject a message",
            "description": "Reject a consumed message using receipt_handle",
            "deprecated": False,
            "safety_validation_required": False,
            "parameters": [
                {
                    "name": "receipt_handle",
                    "in": "body",
                    "required": True,
                    "schema": {"type": "string"},
                },
                {
                    "name": "requeue",
                    "in": "body",
                    "required": False,
                    "schema": {"type": "boolean"},
                },
            ],
            "request_body": None,
        },
    }


def main() -> None:
    args = parse_args()
    spec_path = Path(args.spec_path)
    output_path = Path(args.output_path)
    spec = load_spec(spec_path)
    operations = build_operations(spec)
    if not args.no_include_amqp:
        operations.update(amqp_operations())
    output = {
        "generated_from": str(spec_path),
        "total_operations": len(operations),
        "operations": operations,
    }
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(json.dumps(output, indent=2, sort_keys=True), encoding="utf-8")


if __name__ == "__main__":
    main()
