#!/usr/bin/env python3
"""Generate Python protobuf/gRPC bindings from the cmvr-es source tree."""

from __future__ import annotations

import argparse
import platform
import subprocess
import sys
from pathlib import Path


DEFAULT_CMVR_ES_ROOT = Path("/home/xtkuang/Projects/cmvr/cmvr-es")


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--cmvr-es-root",
        type=Path,
        default=DEFAULT_CMVR_ES_ROOT,
        help="cmvr-es checkout containing protos/ and output/bin/",
    )
    parser.add_argument(
        "--output",
        type=Path,
        default=Path("src"),
        help="output root; generated imports require a top-level cmvr package",
    )
    parser.add_argument(
        "--portable",
        action="store_true",
        help="use python -m grpc_tools.protoc instead of cmvr-es bundled tools",
    )
    return parser.parse_args()


def build_command(args: argparse.Namespace, proto_files: list[Path]) -> list[str]:
    proto_root = args.cmvr_es_root.resolve() / "protos"
    output = args.output.resolve()
    relative_files = [str(path.relative_to(proto_root)) for path in proto_files]

    if args.portable:
        try:
            import grpc_tools
        except ImportError as exc:
            raise RuntimeError(
                "portable generation requires grpcio-tools; install the dev extra"
            ) from exc
        well_known = Path(grpc_tools.__file__).resolve().parent / "_proto"
        return [
            sys.executable,
            "-m",
            "grpc_tools.protoc",
            f"-I{proto_root}",
            f"-I{well_known}",
            f"--python_out={output}",
            f"--pyi_out={output}",
            f"--grpc_python_out={output}",
            *relative_files,
        ]

    protoc = args.cmvr_es_root.resolve() / "output/bin/protoc"
    grpc_plugin = args.cmvr_es_root.resolve() / "output/bin/grpc_python_plugin"
    if not protoc.is_file() or not grpc_plugin.is_file():
        raise FileNotFoundError(
            "cmvr-es protobuf tools are missing; build cmvr-es first or pass --portable"
        )
    well_known = _bundled_well_known_proto_root(args.cmvr_es_root.resolve())
    return [
        str(protoc),
        f"-I{proto_root}",
        f"-I{well_known}",
        f"--python_out={output}",
        f"--pyi_out={output}",
        f"--grpc_python_out={output}",
        f"--plugin=protoc-gen-grpc_python={grpc_plugin}",
        *relative_files,
    ]


def _bundled_well_known_proto_root(cmvr_es_root: Path) -> Path:
    architecture = (
        "arm" if platform.machine().lower() in {"aarch64", "arm64"} else "x86"
    )
    candidates = (
        cmvr_es_root / "dependency" / architecture / "third_party/grpc/v1.76.0/include",
        cmvr_es_root / "dependency/x86/third_party/grpc/v1.76.0/include",
        cmvr_es_root / "dependency/arm/third_party/grpc/v1.76.0/include",
    )
    for candidate in candidates:
        if (candidate / "google/protobuf/timestamp.proto").is_file():
            return candidate
    raise FileNotFoundError(
        "google/protobuf/timestamp.proto was not found in the cmvr-es toolchain"
    )


def main() -> int:
    args = parse_args()
    proto_root = args.cmvr_es_root.resolve() / "protos"
    if not proto_root.is_dir():
        raise FileNotFoundError(f"proto root does not exist: {proto_root}")

    proto_files = sorted((proto_root / "cmvr").rglob("*.proto"))
    if not proto_files:
        raise FileNotFoundError(f"no cmvr proto files found under {proto_root}")

    args.output.mkdir(parents=True, exist_ok=True)
    command = build_command(args, proto_files)
    subprocess.run(command, cwd=proto_root, check=True)
    print(f"generated {len(proto_files)} proto files into {args.output.resolve()}")
    return 0


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