"""
FikoYT Yüz Tanıma Servisi - v3 (InsightFace Native)
InsightFace kütüphanesi doğrudan kullanılır - preprocessing/postprocessing garantili doğru.

RTX 4090 CUDA: ~15-30ms per image
"""

import io
import os
import time
import logging
import numpy as np
from typing import List

import cv2
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse
from PIL import Image

# ─── Logging ─────────────────────────────────────────────────────

logging.basicConfig(
    level=logging.INFO,
    format="[YuzTanima] %(asctime)s %(levelname)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)

# ─── App ─────────────────────────────────────────────────────────

app = FastAPI(title="FikoYT Yüz Tanıma Servisi", version="3.0.0")

# ─── Global State ────────────────────────────────────────────────

face_app = None
gpu_available = False
active_provider = "CPU"

# ─── Startup ─────────────────────────────────────────────────────

@app.on_event("startup")
async def startup():
    global face_app, gpu_available, active_provider

    logger.info("InsightFace yükleniyor...")
    t0 = time.time()

    try:
        from insightface.app import FaceAnalysis

        # Model dizinini ayarla
        model_dir = "/models"
        # insightface modelleri buffalo_l altında bekler
        buffalo_dir = os.path.join(os.path.expanduser("~"), ".insightface", "models", "buffalo_l")
        os.makedirs(buffalo_dir, exist_ok=True)

        # /models'daki dosyaları insightface'in beklediği yere linkle
        for f in os.listdir(model_dir):
            if f.endswith(".onnx"):
                src = os.path.join(model_dir, f)
                dst = os.path.join(buffalo_dir, f)
                if not os.path.exists(dst):
                    os.symlink(src, dst)

        # GPU provider
        providers = ["CUDAExecutionProvider", "CPUExecutionProvider"]

        face_app = FaceAnalysis(
            name="buffalo_l",
            allowed_modules=["detection", "recognition"],
            providers=providers,
        )
        face_app.prepare(ctx_id=0, det_size=(640, 640), det_thresh=0.3)

        # Hangi provider aktif?
        if hasattr(face_app.det_model, 'session'):
            used = face_app.det_model.session.get_providers()
            if "CUDAExecutionProvider" in used:
                gpu_available = True
                active_provider = "CUDA"
            elif "TensorrtExecutionProvider" in used:
                gpu_available = True
                active_provider = "TensorRT"
            else:
                active_provider = "CPU"

        logger.info(f"InsightFace yüklendi ({time.time() - t0:.1f}s)")

        # Warmup
        logger.info("GPU warmup...")
        dummy = np.zeros((640, 640, 3), dtype=np.uint8)
        for _ in range(3):
            face_app.get(dummy)
        logger.info("Warmup tamamlandı")

    except Exception as e:
        logger.error(f"InsightFace yüklenemedi: {e}")
        import traceback
        traceback.print_exc()

    logger.info(f"Toplam başlatma: {time.time() - t0:.1f}s")
    logger.info(f"Provider: {active_provider}, GPU: {gpu_available}")


# ─── Endpoints ───────────────────────────────────────────────────

@app.post("/detect")
async def detect_endpoint(image: UploadFile = File(...)):
    """Resimden yüz tespit et ve 512D descriptor çıkar."""
    if face_app is None:
        raise HTTPException(status_code=503, detail="Model henüz yüklenmedi")

    t0 = time.time()

    # Resmi oku
    contents = await image.read()
    try:
        pil_img = Image.open(io.BytesIO(contents)).convert("RGB")
        # InsightFace BGR bekler (OpenCV formatı)
        img_rgb = np.array(pil_img)
        img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Resim okunamadı: {e}")

    t_read = time.time()

    # InsightFace ile detect + recognize (tek çağrı)
    faces = face_app.get(img_bgr)
    t_det = time.time()

    # Sonuçları formatla
    results = []
    img_h, img_w = img_bgr.shape[:2]

    for face in faces:
        bbox = face.bbox.astype(int).tolist()  # [x1, y1, x2, y2]
        x1, y1, x2, y2 = bbox
        x1 = max(0, x1)
        y1 = max(0, y1)
        x2 = min(img_w, x2)
        y2 = min(img_h, y2)
        w, h = x2 - x1, y2 - y1

        if w < 10 or h < 10:
            continue

        # Descriptor (512D, L2 normalized)
        descriptor = []
        if face.embedding is not None:
            emb = face.embedding
            norm = np.linalg.norm(emb)
            if norm > 0:
                emb = emb / norm
            descriptor = emb.tolist()

        results.append({
            "bbox": [x1, y1, w, h],
            "descriptor": descriptor,
            "det_score": float(face.det_score),
        })

    t_end = time.time()
    total_ms = (t_end - t0) * 1000
    det_ms = (t_det - t_read) * 1000
    read_ms = (t_read - t0) * 1000

    logger.info(
        f"{len(results)} yüz | toplam={total_ms:.0f}ms "
        f"(read={read_ms:.0f}ms detect+recog={det_ms:.0f}ms) | "
        f"{img_w}x{img_h}px | {active_provider}"
    )

    return JSONResponse({
        "faces": results,
        "processing_time_ms": total_ms,
        "detail_ms": {"detect": round(det_ms, 1), "recognize": 0},
        "image_size": {"width": img_w, "height": img_h},
        "provider": active_provider,
    })


@app.get("/health")
async def health():
    return {
        "status": "ok",
        "model_loaded": face_app is not None,
        "gpu": gpu_available,
        "provider": active_provider,
    }


@app.get("/gpu")
async def gpu_info():
    info = {
        "gpu_available": gpu_available,
        "active_provider": active_provider,
    }
    try:
        import subprocess
        result = subprocess.run(
            ["nvidia-smi", "--query-gpu=name,memory.total,memory.used,utilization.gpu",
             "--format=csv,noheader,nounits"],
            capture_output=True, text=True, timeout=5,
        )
        if result.returncode == 0:
            parts = result.stdout.strip().split(", ")
            info["gpu_name"] = parts[0]
            info["gpu_memory_total_mb"] = int(parts[1])
            info["gpu_memory_used_mb"] = int(parts[2])
            info["gpu_utilization_percent"] = int(parts[3])
    except Exception:
        pass
    return info
