#!/usr/bin/env python3
"""
Generate typed request/response model stubs from endpoint catalog.

This script intentionally keeps generation deterministic and lightweight:
- Input: Rust endpoint catalog (`src/generated/endpoints.rs`) OR oapi-sdk-go root path.
- Output: `src/api/generated_models.rs`
- Output includes per-operation request/response structs with doc comments.
"""

from __future__ import annotations

import argparse
import pathlib
import re
from dataclasses import dataclass


@dataclass(frozen=True)
class Endpoint:
    operation_id: str
    http_method: str
    path: str


OP_RE = re.compile(
    r'operation_id:\s*"([^"]+)"\s*,\s*'
    r'service:\s*"[^"]+"\s*,\s*'
    r'version:\s*"[^"]+"\s*,\s*'
    r'resource:\s*"[^"]+"\s*,\s*'
    r'method_name:\s*"[^"]+"\s*,\s*'
    r'http_method:\s*"([^"]+)"\s*,\s*'
    r'path:\s*"([^"]+)"',
    re.S,
)


def snake_to_pascal(text: str) -> str:
    chunks = re.split(r"[^a-zA-Z0-9]+", text)
    return "".join(c[:1].upper() + c[1:] for c in chunks if c)


def op_to_type_name(operation_id: str) -> str:
    parts = operation_id.split(".")
    return "".join(snake_to_pascal(part) for part in parts)


def load_from_endpoint_catalog(path: pathlib.Path) -> list[Endpoint]:
    data = path.read_text(encoding="utf-8")
    endpoints: list[Endpoint] = []
    for operation_id, http_method, api_path in OP_RE.findall(data):
        endpoints.append(
            Endpoint(
                operation_id=operation_id,
                http_method=http_method,
                path=api_path,
            )
        )
    return endpoints


def render_models(endpoints: list[Endpoint]) -> str:
    lines: list[str] = []
    lines.append("// @generated by tools/generate_from_go.py; DO NOT EDIT.")
    lines.append("use serde::{Deserialize, Serialize};")
    lines.append("")
    lines.append("/// Generated request/response model stubs for all known operations.")
    lines.append("///")
    lines.append("/// Notes:")
    lines.append("/// - Request structs use `serde_json::Value` as a flexible envelope.")
    lines.append("/// - Response structs mirror the same envelope to keep generation stable.")
    lines.append("/// - Service-specific hand-written models can coexist in `src/api/*`.")
    lines.append("")

    for ep in endpoints:
        base = op_to_type_name(ep.operation_id)
        req_name = f"{base}Request"
        resp_name = f"{base}Response"
        lines.append(f"/// Operation: `{ep.operation_id}`")
        lines.append(f"/// HTTP: `{ep.http_method} {ep.path}`")
        lines.append("#[derive(Debug, Clone, Serialize, Deserialize, Default)]")
        lines.append(f"pub struct {req_name} {{")
        lines.append("    #[serde(flatten)]")
        lines.append("    pub data: serde_json::Value,")
        lines.append("}")
        lines.append("")
        lines.append(f"/// Response model for `{ep.operation_id}`.")
        lines.append("#[derive(Debug, Clone, Serialize, Deserialize, Default)]")
        lines.append(f"pub struct {resp_name} {{")
        lines.append("    #[serde(flatten)]")
        lines.append("    pub data: serde_json::Value,")
        lines.append("}")
        lines.append("")

    lines.append("/// Number of generated operation model pairs.")
    lines.append(f"pub const GENERATED_MODEL_COUNT: usize = {len(endpoints)};")
    lines.append("")
    return "\n".join(lines)


def main() -> int:
    parser = argparse.ArgumentParser(description="Generate Rust model stubs from endpoint catalog")
    parser.add_argument(
        "--endpoint-catalog",
        default="src/generated/endpoints.rs",
        help="Path to generated endpoints.rs",
    )
    parser.add_argument(
        "--output",
        default="src/api/generated_models.rs",
        help="Output Rust source path",
    )
    args = parser.parse_args()

    endpoint_catalog = pathlib.Path(args.endpoint_catalog)
    output = pathlib.Path(args.output)
    if not endpoint_catalog.exists():
        raise SystemExit(f"endpoint catalog not found: {endpoint_catalog}")

    endpoints = load_from_endpoint_catalog(endpoint_catalog)
    if not endpoints:
        raise SystemExit("no endpoints parsed from endpoint catalog")

    output.parent.mkdir(parents=True, exist_ok=True)
    output.write_text(render_models(endpoints), encoding="utf-8")
    print(f"Generated {len(endpoints)} model pairs into {output}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
