"""إعادة التعرّف عبر الكاميرات (re-ID) — تتبّع نفس الشخص عبر كاميرات الفرع.

الاستدلال الثقيل على السحابة (كنمط الـVLM): من لقطة الحدث → قصاصة أكبر شخص (YOLO)
→ متجه مظهر OSNet 512-بُعد مطبَّع L2 → تخزين → مطابقة **جيب التمام** عبر الكاميرات
ضمن نافذة زمنية = مسار الشخص. النماذج: veedon-detect-v1.onnx (كشف) + veedon-reid-v1 (OSNet, MIT).
"""
import io
import os
from datetime import timedelta

import numpy as np
from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy import select
from sqlalchemy.orm import Session

from .db import DATA_DIR, get_db
from .models import CameraEventAssignment, ReidEmbedding, utcnow

router = APIRouter(prefix="/api/insights", tags=["reid"])
_MODELS = os.path.join(DATA_DIR, "models")
_sess = {}
_MEAN = np.array([0.485, 0.456, 0.406], np.float32)
_STD = np.array([0.229, 0.224, 0.225], np.float32)


def _session(name):
    if name not in _sess:
        import onnxruntime as ort
        _sess[name] = ort.InferenceSession(os.path.join(_MODELS, name),
                                           providers=["CPUExecutionProvider"])
    return _sess[name]


def _persons(jpeg: bytes, conf_thr: float = 0.4):
    """(الصورة، كل صناديق "person") عبر YOLOv8-ONNX (person = الصنف 0)."""
    from PIL import Image
    img = Image.open(io.BytesIO(jpeg)).convert("RGB")
    w0, h0 = img.size
    try:
        s = _session("veedon-detect-v1.onnx")
    except Exception:
        return img, []
    size = 640
    r = min(size / w0, size / h0)
    nw, nh = round(w0 * r), round(h0 * r)
    canvas = Image.new("RGB", (size, size), (114, 114, 114))
    px, py = (size - nw) // 2, (size - nh) // 2
    canvas.paste(img.resize((nw, nh), Image.BILINEAR), (px, py))
    x = np.asarray(canvas, np.float32).transpose(2, 0, 1)[None] / 255.0
    out = np.squeeze(s.run(None, {s.get_inputs()[0].name: x})[0]).T   # [8400, 84]
    boxes = []
    for row in out[out[:, 4] > conf_thr]:                            # person = العمود 4
        cx, cy, bw, bh = row[:4]
        x1 = max(0, (cx - bw / 2 - px) / r); y1 = max(0, (cy - bh / 2 - py) / r)
        x2 = min(w0, (cx + bw / 2 - px) / r); y2 = min(h0, (cy + bh / 2 - py) / r)
        if x2 - x1 >= 8 and y2 - y1 >= 16:
            boxes.append((int(x1), int(y1), int(x2), int(y2)))
    return img, boxes


def _largest_person(jpeg: bytes):
    img, boxes = _persons(jpeg)
    if not boxes:
        return None
    x1, y1, x2, y2 = max(boxes, key=lambda b: (b[2] - b[0]) * (b[3] - b[1]))
    return img.crop((x1, y1, x2, y2))


def redact(jpeg: bytes, mode: str = "persons") -> bytes:
    """تعتيم الأشخاص/الوجوه في اللقطة (خصوصية PDPL/GDPR) قبل التخزين/التصدير.
    mode=persons: يعتّم كامل جسم الشخص · faces: يعتّم الربع العلوي (الرأس تقريباً)."""
    from PIL import ImageFilter
    img, boxes = _persons(jpeg)
    if not boxes:
        return jpeg
    for (x1, y1, x2, y2) in boxes:
        if mode == "faces":
            y2 = y1 + max(10, (y2 - y1) // 4)
        region = img.crop((x1, y1, x2, y2)).filter(
            ImageFilter.GaussianBlur(radius=max(6, (x2 - x1) // 8)))
        img.paste(region, (x1, y1))
    buf = io.BytesIO()
    img.save(buf, "JPEG", quality=85)
    return buf.getvalue()


def redaction_enabled(db: Session, camera_id, feature="privacy_redaction"):
    """هل تعتيم الخصوصية مفعّل على هذه الكاميرا؟ (يُعيد mode أو None)."""
    from .models import EventType
    a = db.scalar(select(CameraEventAssignment)
                  .join(EventType, CameraEventAssignment.event_type_id == EventType.id)
                  .where(CameraEventAssignment.camera_id == camera_id,
                         CameraEventAssignment.enabled.is_(True),
                         EventType.feature_key == feature))
    return (a.config or {}).get("mode", "persons") if a else None


def embed(jpeg: bytes):
    """قصاصة الشخص → متجه OSNet 512-بُعد مطبَّع L2 (أو None إن لا شخص/نموذج)."""
    from PIL import Image
    crop = _largest_person(jpeg)
    if crop is None:
        return None
    crop = crop.resize((128, 256), Image.BILINEAR)               # OSNet: 256×128
    x = ((np.asarray(crop, np.float32) / 255.0) - _MEAN) / _STD
    x = x.transpose(2, 0, 1)
    try:
        s = _session("veedon-reid-v1")
    except Exception:
        return None
    bn = s.get_inputs()[0].shape[0]                              # دفعة ثابتة (16)
    batch = np.zeros((bn, 3, 256, 128), np.float32)
    batch[0] = x
    v = s.run(None, {s.get_inputs()[0].name: batch})[0][0]
    return (v / (np.linalg.norm(v) + 1e-9)).astype(np.float32)


def cosine(a, b) -> float:
    return float(np.dot(a, b))                                   # كلاهما مطبَّع L2


def reid_enabled(db: Session, camera_id) -> bool:
    """هل ميزة re-ID مفعّلة على هذه الكاميرا؟ (نتجنّب توليد المتجهات لكل حدث)."""
    from .models import EventType
    q = (select(CameraEventAssignment)
         .join(EventType, CameraEventAssignment.event_type_id == EventType.id)
         .where(CameraEventAssignment.camera_id == camera_id,
                CameraEventAssignment.enabled.is_(True),
                EventType.feature_key == "reid"))
    return db.scalar(q) is not None


def index_event(db: Session, event, jpeg: bytes, branch_id, camera: str) -> bool:
    """يخزّن متجه مظهر الشخص للحدث (يُستدعى خلفياً عند تفعيل re-ID)."""
    v = embed(jpeg)
    if v is None:
        return False
    db.add(ReidEmbedding(event_id=event.id, branch_id=branch_id, camera=camera,
                         detected_at=event.detected_at, embedding=v.tolist()))
    db.commit()
    return True


def build_track(base: ReidEmbedding, candidates, threshold: float):
    """مسار الشخص: أفضل تطابق (≥ عتبة) لكل كاميرا، مرتّباً زمنياً."""
    qv = np.asarray(base.embedding, np.float32)
    best = {}
    for c in candidates:
        sim = cosine(qv, np.asarray(c.embedding, np.float32))
        if sim >= threshold and (c.camera not in best or sim > best[c.camera][1]):
            best[c.camera] = (c, sim)
    return sorted(best.values(), key=lambda cs: cs[0].detected_at or utcnow())


@router.get("/redact_preview/{event_id}")
def redact_preview(event_id: str, mode: str = "persons"):
    """معاينة تعتيم الخصوصية على لقطة حدث موجودة (قبل/بعد في اللوحة)."""
    import os
    from .device import _snapshot_path
    p = _snapshot_path(event_id)
    if not os.path.isfile(p):
        raise HTTPException(404, "no snapshot for this event")
    with open(p, "rb") as f:
        raw = f.read()
    m = "faces" if mode == "faces" else "persons"
    return Response(redact(raw, m), media_type="image/jpeg")


@router.get("/track/{event_id}")
def track(event_id: str, window_min: int = 30, threshold: float = 0.5,
          db: Session = Depends(get_db)):
    """مسار الشخص عبر كاميرات الفرع حول حدث معيّن (خط زمني بصري موحّد للشخص)."""
    base = db.scalar(select(ReidEmbedding).where(ReidEmbedding.event_id == event_id))
    if not base:
        raise HTTPException(404, "لا متجه لهذا الحدث — فعّل re-ID على الكاميرا")
    lo = base.detected_at - timedelta(minutes=window_min)
    hi = base.detected_at + timedelta(minutes=window_min)
    cands = db.scalars(select(ReidEmbedding).where(
        ReidEmbedding.branch_id == base.branch_id,
        ReidEmbedding.detected_at >= lo,
        ReidEmbedding.detected_at <= hi)).all()
    path = build_track(base, cands, threshold)
    return {"event_id": event_id, "branch_id": str(base.branch_id),
            "threshold": threshold, "window_min": window_min,
            "cameras": len(path),
            "path": [{"camera": c.camera, "event_id": str(c.event_id),
                      "at": c.detected_at.isoformat() if c.detected_at else None,
                      "similarity": round(sim, 3)} for c, sim in path]}
