"""
Persistent Python YOLO detection daemon (stdin/stdout JSON-per-line protocol).

Loads a YOLOv11 model and serves inference requests over stdin/stdout.
Managed as a subprocess by PythonDetectBridge in Poe2Trade.Screen.

Request:  {"cmd": "detect", "imageBase64": "...", "conf": 0.3, "iou": 0.45, "imgsz": 640}
Response: {"ok": true, "count": 3, "inferenceMs": 12.5, "detections": [...]}
"""

import sys
import json
import time

_models = {}


def _redirect_stdout_to_stderr():
    """Redirect stdout to stderr so library print() calls don't corrupt the JSON protocol."""
    real_stdout = sys.stdout
    sys.stdout = sys.stderr
    return real_stdout


def _restore_stdout(real_stdout):
    sys.stdout = real_stdout


def load_model(name="enemy-v1"):
    if name in _models:
        return _models[name]

    import os
    from ultralytics import YOLO

    model_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "models")

    # Prefer TensorRT engine if available, fall back to .pt
    engine_path = os.path.join(model_dir, f"{name}.engine")
    pt_path = os.path.join(model_dir, f"{name}.pt")
    model_path = engine_path if os.path.exists(engine_path) else pt_path

    if not os.path.exists(model_path):
        raise FileNotFoundError(f"Model not found: {pt_path} (also checked {engine_path})")

    sys.stderr.write(f"Loading YOLO model '{name}' from {model_path}...\n")
    sys.stderr.flush()

    real_stdout = _redirect_stdout_to_stderr()
    try:
        model = YOLO(model_path)
        # Warmup with dummy inference (triggers CUDA init)
        import numpy as np
        dummy = np.zeros((640, 640, 3), dtype=np.uint8)
        model.predict(dummy, verbose=False)
    finally:
        _restore_stdout(real_stdout)

    _models[name] = model
    sys.stderr.write(f"YOLO model '{name}' loaded and warmed up.\n")
    sys.stderr.flush()
    return model


def handle_detect(req):
    import base64
    import io
    import numpy as np
    from PIL import Image

    image_base64 = req.get("imageBase64")
    if not image_base64:
        return {"ok": False, "error": "Missing imageBase64"}

    img_bytes = base64.b64decode(image_base64)
    img = np.array(Image.open(io.BytesIO(img_bytes)))
    # PIL gives RGB, but ultralytics model.predict() assumes numpy arrays are BGR
    img = img[:, :, ::-1]

    conf = req.get("conf", 0.3)
    iou = req.get("iou", 0.45)
    imgsz = req.get("imgsz", 640)
    model_name = req.get("model", "enemy-v1")

    model = load_model(model_name)

    real_stdout = _redirect_stdout_to_stderr()
    try:
        start = time.perf_counter()
        results = model.predict(img, conf=conf, iou=iou, imgsz=imgsz, verbose=False)
        inference_ms = (time.perf_counter() - start) * 1000
    finally:
        _restore_stdout(real_stdout)

    detections = []
    for result in results:
        boxes = result.boxes
        if boxes is None:
            continue
        for i in range(len(boxes)):
            box = boxes[i]
            x1, y1, x2, y2 = box.xyxy[0].tolist()
            x, y = int(x1), int(y1)
            w, h = int(x2 - x1), int(y2 - y1)
            cx, cy = x + w // 2, y + h // 2
            class_id = int(box.cls[0].item())
            class_name = result.names[class_id] if result.names else str(class_id)
            confidence = float(box.conf[0].item())

            detections.append({
                "class": class_name,
                "classId": class_id,
                "confidence": round(confidence, 4),
                "x": x, "y": y, "width": w, "height": h,
                "cx": cx, "cy": cy,
            })

    return {
        "ok": True,
        "count": len(detections),
        "inferenceMs": round(inference_ms, 2),
        "detections": detections,
    }


def handle_request(req):
    cmd = req.get("cmd")
    if cmd == "detect":
        return handle_detect(req)
    if cmd == "ping":
        return {"ok": True, "pong": True}
    return {"ok": False, "error": f"Unknown command: {cmd}"}


def main():
    # Signal ready immediately — model loads lazily on first detect request
    sys.stdout.write(json.dumps({"ok": True, "ready": True}) + "\n")
    sys.stdout.flush()

    for line in sys.stdin:
        line = line.strip()
        if not line:
            continue
        try:
            req = json.loads(line)
            resp = handle_request(req)
        except Exception as e:
            resp = {"ok": False, "error": str(e)}
        sys.stdout.write(json.dumps(resp) + "\n")
        sys.stdout.flush()


if __name__ == "__main__":
    main()
