#!/usr/bin/env python3
"""Export OpenAPI schema from FastAPI app without running the server.

Usage:
    python scripts/export-openapi.py > openapi.json
    python scripts/export-openapi.py -o agentarea-webapp/src/api/openapi.json
"""

import argparse
import json
import os
import sys
from pathlib import Path

# Provide dummy values for required env vars so the app can initialize
# without real services. Only the OpenAPI schema is extracted — no connections are made.
_DUMMY_ENV = {
    "WORKFLOW__TEMPORAL_SERVER_URL": "http://localhost:7233",
    "WORKFLOW__TEMPORAL_NAMESPACE": "default",
    "WORKFLOW__TEMPORAL_TASK_QUEUE": "agent-tasks",
    "KRATOS_JWKS_B64": "eyJrZXlzIjpbXX0=",  # {"keys":[]} - empty JWKS, enough for schema export
}
for key, value in _DUMMY_ENV.items():
    os.environ.setdefault(key, value)

# Add the platform root so uv workspace imports resolve
platform_root = Path(__file__).resolve().parent.parent / "agentarea-platform"
sys.path.insert(0, str(platform_root / "apps" / "api"))
sys.path.insert(0, str(platform_root / "libs" / "common"))
sys.path.insert(0, str(platform_root / "libs" / "execution"))
sys.path.insert(0, str(platform_root / "libs" / "llm"))
sys.path.insert(0, str(platform_root / "libs" / "mcp"))


def _sort_dict(obj):
    """Recursively sort dict keys for deterministic output."""
    if isinstance(obj, dict):
        return {k: _sort_dict(v) for k, v in sorted(obj.items())}
    if isinstance(obj, list):
        return [_sort_dict(item) for item in obj]
    return obj


def _normalize_operation_ids(schema: dict) -> dict:
    """Normalize duplicate operation IDs to use deterministic path+method naming.

    FastAPI appends the HTTP method to deduplicate operation IDs, but the suffix
    chosen depends on route registration order which varies across environments.
    This normalizes all operations to use `{operationId_base}_{method}` format.
    """
    paths = schema.get("paths", {})
    seen_ids: dict[str, list[tuple[str, str]]] = {}

    # First pass: collect all operationIds and where they appear
    for path, methods in paths.items():
        for method, operation in methods.items():
            if not isinstance(operation, dict):
                continue
            op_id = operation.get("operationId")
            if op_id:
                seen_ids.setdefault(op_id, []).append((path, method))

    # Second pass: find duplicate references and normalize
    # Build a map of all operationIds used across all paths
    all_op_ids: dict[str, str] = {}  # (path, method) -> operationId
    for path, methods in paths.items():
        for method, operation in methods.items():
            if not isinstance(operation, dict):
                continue
            op_id = operation.get("operationId")
            if op_id:
                all_op_ids[(path, method)] = op_id

    # Find groups that share the same base (same path, multiple methods pointing
    # to same handler). Normalize by always using {base}_{method} format.
    # Detect shared handlers: same path, different methods, same summary
    for path, methods in paths.items():
        ops = {m: o for m, o in methods.items() if isinstance(o, dict) and "operationId" in o}
        if len(ops) <= 1:
            continue

        # Check if multiple methods share the same summary (same handler)
        summary_groups: dict[str, list[str]] = {}
        for method, op in ops.items():
            summary = op.get("summary", "")
            summary_groups.setdefault(summary, []).append(method)

        for summary, group_methods in summary_groups.items():
            if len(group_methods) <= 1:
                continue
            # These share a handler — normalize their operationIds
            # Use the first operationId's base (strip any existing method suffix)
            first_op_id = ops[group_methods[0]]["operationId"]
            # Strip trailing _get/_post/_put/_delete/_patch/_head/_options
            base = first_op_id
            for suffix in ("_get", "_post", "_put", "_delete", "_patch", "_head", "_options"):
                if base.endswith(suffix):
                    base = base[: -len(suffix)]
                    break

            for method in group_methods:
                ops[method]["operationId"] = f"{base}_{method}"

    return schema


def export_schema() -> dict:
    from agentarea_api.main import create_app

    app = create_app()
    schema = app.openapi()
    # Normalize duplicate operation IDs for deterministic output
    _normalize_operation_ids(schema)
    # Sort keys for deterministic output across environments
    return _sort_dict(schema)


def main():
    parser = argparse.ArgumentParser(description="Export OpenAPI schema")
    parser.add_argument("-o", "--output", help="Output file path (default: stdout)")
    args = parser.parse_args()

    schema = export_schema()
    spec_json = json.dumps(schema, indent=2)

    if args.output:
        Path(args.output).write_text(spec_json + "\n")
        print(f"Wrote OpenAPI spec to {args.output}", file=sys.stderr)
    else:
        print(spec_json)


if __name__ == "__main__":
    main()
