#!/usr/bin/env python3
"""TensorRT engine conversion script.

Converts ONNX models to TensorRT FP16 .engine files for Jetson deployment.
Wraps trtexec CLI (available on Jetson with JetPack installed).

Usage:
    # Convert a single model
    python scripts/convert_to_trt.py --onnx weights/litesam.onnx --output /opt/engines/litesam.engine

    # Convert all known models from weights/ to engines/
    python scripts/convert_to_trt.py --all --onnx-dir weights/ --engine-dir /opt/engines/

    # Dry run (check trtexec availability)
    python scripts/convert_to_trt.py --check

Requires:
    - NVIDIA TensorRT (trtexec in PATH) — available on Jetson with JetPack
    - NOT available on dev/CI machines — script exits cleanly with a message
"""

from __future__ import annotations

import argparse
import os
import shutil
import subprocess
import sys

# Known models and their expected ONNX filenames
_MODELS = {
    "superpoint": "superpoint.onnx",
    "lightglue": "lightglue.onnx",
    "xfeat": "xfeat.onnx",
    "dinov2": "dinov2.onnx",
    "litesam": "litesam.onnx",
}


def find_trtexec() -> str | None:
    """Find trtexec binary in PATH or common Jetson locations."""
    path = shutil.which("trtexec")
    if path:
        return path
    # Common Jetson paths
    for candidate in [
        "/usr/src/tensorrt/bin/trtexec",
        "/usr/local/cuda/bin/trtexec",
    ]:
        if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
            return candidate
    return None


def convert_onnx_to_engine(
    onnx_path: str,
    engine_path: str,
    fp16: bool = True,
    workspace_mb: int = 1024,
    trtexec_path: str | None = None,
) -> bool:
    """Run trtexec to convert ONNX → TensorRT engine.

    Returns True on success, False on failure.
    """
    trtexec = trtexec_path or find_trtexec()
    if not trtexec:
        print("ERROR: trtexec not found. Install TensorRT or run on Jetson with JetPack.")
        return False

    if not os.path.isfile(onnx_path):
        print(f"ERROR: ONNX file not found: {onnx_path}")
        return False

    os.makedirs(os.path.dirname(engine_path) or ".", exist_ok=True)

    cmd = [
        trtexec,
        f"--onnx={onnx_path}",
        f"--saveEngine={engine_path}",
        f"--workspace={workspace_mb}",
    ]
    if fp16:
        cmd.append("--fp16")

    print(f"  Converting: {onnx_path} → {engine_path}")
    print(f"  Command: {' '.join(cmd)}")

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
        if result.returncode == 0:
            size_mb = os.path.getsize(engine_path) / (1024 * 1024)
            print(f"  OK: {engine_path} ({size_mb:.1f} MB)")
            return True
        else:
            print(f"  FAIL (exit {result.returncode})")
            if result.stderr:
                print(f"  stderr: {result.stderr[:500]}")
            return False
    except subprocess.TimeoutExpired:
        print("  FAIL: trtexec timed out (>600s)")
        return False
    except FileNotFoundError:
        print(f"  FAIL: trtexec not found at {trtexec}")
        return False


def main() -> int:
    parser = argparse.ArgumentParser(description="Convert ONNX models to TensorRT FP16 engines")
    parser.add_argument("--onnx", help="Input ONNX model path")
    parser.add_argument("--output", help="Output .engine path")
    parser.add_argument("--all", action="store_true", help="Convert all known models")
    parser.add_argument("--onnx-dir", default="weights", help="Directory with ONNX files")
    parser.add_argument("--engine-dir", default="/opt/engines", help="Output engine directory")
    parser.add_argument("--no-fp16", action="store_true", help="Disable FP16 (use FP32)")
    parser.add_argument("--workspace", type=int, default=1024, help="TRT workspace (MB)")
    parser.add_argument("--check", action="store_true", help="Check trtexec availability only")
    args = parser.parse_args()

    # Check mode
    if args.check:
        trtexec = find_trtexec()
        if trtexec:
            print(f"trtexec found: {trtexec}")
            return 0
        else:
            print("trtexec not found. Not on Jetson or TensorRT not installed.")
            return 1

    fp16 = not args.no_fp16

    # Single model conversion
    if args.onnx and args.output:
        ok = convert_onnx_to_engine(args.onnx, args.output, fp16=fp16, workspace_mb=args.workspace)
        return 0 if ok else 1

    # Batch conversion
    if args.all:
        trtexec = find_trtexec()
        if not trtexec:
            print("trtexec not found. Conversion requires Jetson with JetPack installed.")
            return 1

        print(f"Converting all known models from {args.onnx_dir}/ → {args.engine_dir}/")
        success = 0
        fail = 0
        for model_name, onnx_file in _MODELS.items():
            onnx_path = os.path.join(args.onnx_dir, onnx_file)
            engine_path = os.path.join(args.engine_dir, f"{model_name}.engine")
            if not os.path.isfile(onnx_path):
                print(f"  SKIP {model_name}: {onnx_path} not found")
                continue
            ok = convert_onnx_to_engine(
                onnx_path, engine_path,
                fp16=fp16, workspace_mb=args.workspace, trtexec_path=trtexec,
            )
            if ok:
                success += 1
            else:
                fail += 1

        print(f"\nDone: {success} converted, {fail} failed")
        return 1 if fail > 0 else 0

    parser.print_help()
    return 1


if __name__ == "__main__":
    sys.exit(main())
