# -*- coding: utf-8 -*-
# api.py — versão otimizada com carregamento leve e assíncrono para cPanel

import os
import base64
import numpy as np
import cv2
import torch
import warnings
import gc
import threading
from flask import Flask, request, jsonify
from flask_cors import CORS

# ======================================
# ⚙️ LIMITES DE PERFORMANCE / THREADS
# ======================================
os.environ.update({
    "OMP_NUM_THREADS": "1",
    "OPENBLAS_NUM_THREADS": "1",
    "MKL_NUM_THREADS": "1",
    "NUMEXPR_NUM_THREADS": "1",
    "VECLIB_MAXIMUM_THREADS": "1",
    "BLIS_NUM_THREADS": "1",
    "OMP_WAIT_POLICY": "PASSIVE",
    "KMP_INIT_AT_FORK": "FALSE",
    "OPENBLAS_VERBOSE": "0",
    "TORCH_HOME": os.path.join(os.getcwd(), "model_cache"),
})
os.makedirs(os.environ["TORCH_HOME"], exist_ok=True)

warnings.filterwarnings("ignore", category=UserWarning)
torch.set_num_threads(1)
if hasattr(torch, "set_num_interop_threads"):
    torch.set_num_interop_threads(1)
try:
    cv2.setNumThreads(1)
except Exception:
    pass

# ======================================
# ⚙️ CONFIG GERAL
# ======================================
DEVICE = "cpu"
EMB_FILE = "classifier/usuarios_centroides.npy"
IMAGE_SIZE = 160
MARGIN = 10
TOLERANCIA = 1.0
LIMITE_CONFIANCA_API = 10.0

print("🚀 Inicializando API Facial Leve...")
print(f"🧠 Dispositivo em uso: {DEVICE}")

# ======================================
# 🔄 VARIÁVEIS GLOBAIS
# ======================================
mtcnn = None
resnet = None
usuarios = {}
modelos_carregados = False

# ======================================
# 🧠 CARREGAMENTO ASSÍNCRONO DOS MODELOS
# ======================================
def carregar_modelos():
    """Carrega modelos em segundo plano"""
    global mtcnn, resnet, usuarios, modelos_carregados
    try:
        from facenet_pytorch import MTCNN, InceptionResnetV1
        print("🧠 Carregando MTCNN e ResNet em background...")

        mtcnn = MTCNN(
            image_size=IMAGE_SIZE,
            margin=MARGIN,
            min_face_size=30,
            thresholds=[0.6, 0.7, 0.7],
            factor=0.709,
            post_process=True,
            device=DEVICE
        ).eval()

        resnet = InceptionResnetV1(pretrained="vggface2").eval().to(DEVICE)

        if os.path.exists(EMB_FILE):
            usuarios = np.load(EMB_FILE, allow_pickle=True).item()
            for k in list(usuarios.keys()):
                usuarios[k] = usuarios[k].astype(np.float32)
            print(f"✅ {len(usuarios)} centróides carregados.")
        else:
            usuarios = {}
            print("⚠️ Nenhum arquivo de centróides encontrado.")

        modelos_carregados = True
        print("✅ Modelos carregados e prontos para uso!")

    except Exception as e:
        print(f"❌ Erro ao carregar modelos: {e}")
        modelos_carregados = False


# Carrega os modelos sem travar o servidor
threading.Thread(target=carregar_modelos, daemon=True).start()

# ======================================
# 🧮 FUNÇÕES AUXILIARES
# ======================================
def dist_to_confidence(dist, threshold=TOLERANCIA):
    return max(0, min(100, 100 * (1 - dist / threshold))) if dist < threshold else 0.0


def liberar_memoria():
    """Evita acumular memória após requisições"""
    torch.cuda.empty_cache() if torch.cuda.is_available() else None
    gc.collect()


# ======================================
# 🧠 FUNÇÃO DE RECONHECIMENTO
# ======================================
@torch.no_grad()
def check_face_match(frame, target_user=""):
    if not modelos_carregados:
        return {"authorized": False, "message": "Modelos ainda carregando... aguarde alguns segundos."}

    try:
        boxes, probs = mtcnn.detect(frame)
        if boxes is None or len(boxes) == 0:
            return {"authorized": False, "message": "Nenhum rosto detectado."}
        (x1, y1, x2, y2) = [int(v) for v in boxes[0]]

        face_tensor = mtcnn(frame)
        if face_tensor is None:
            return {"authorized": False, "message": "Falha ao recortar a face."}

        embedding = resnet(face_tensor.to(DEVICE).unsqueeze(0)).cpu().numpy().flatten()

        melhor_nome, menor_dist = "Desconhecido", float("inf")
        for nome_usu, centro in usuarios.items():
            dist = np.linalg.norm(centro.flatten() - embedding)
            if dist < menor_dist:
                menor_dist, melhor_nome = dist, nome_usu

        confianca = dist_to_confidence(menor_dist)
        autorizado = confianca >= LIMITE_CONFIANCA_API

        return {
            "authorized": bool(autorizado),
            "recognized_user": melhor_nome,
            "confidence": float(round(confianca, 2)),
            "distance": float(round(menor_dist, 4)),
            "message": (
                f"✅ {melhor_nome} reconhecido ({confianca:.1f}%)"
                if autorizado else
                f"⚠️ {melhor_nome} detectado ({confianca:.1f}%), abaixo do limite."
            )
        }

    except Exception as e:
        return {"authorized": False, "message": f"Erro interno: {e}"}


# ======================================
# 🌐 FLASK APP
# ======================================
app = Flask(__name__)
CORS(app)


@app.route("/api/verify", methods=["POST"])
def verify_face_api():
    if not modelos_carregados:
        return jsonify({"authorized": False, "message": "Modelos ainda carregando..."}), 503

    data = request.get_json(silent=True)
    if not data or "username" not in data or "image_base64" not in data:
        return jsonify({"authorized": False, "message": "JSON inválido."}), 400

    try:
        frame = cv2.imdecode(np.frombuffer(base64.b64decode(data["image_base64"]), np.uint8), cv2.IMREAD_COLOR)
        result = check_face_match(frame, data["username"])
    except Exception as e:
        result = {"authorized": False, "message": f"Erro interno: {e}"}

    liberar_memoria()
    return jsonify(result)


@app.route("/api/auto_verify", methods=["POST"])
def auto_verify_face():
    if not modelos_carregados:
        return jsonify({"authorized": False, "message": "Modelos ainda carregando..."}), 503

    data = request.get_json(silent=True)
    if not data or "image_base64" not in data:
        return jsonify({"authorized": False, "message": "JSON inválido."}), 400

    try:
        frame = cv2.imdecode(np.frombuffer(base64.b64decode(data["image_base64"]), np.uint8), cv2.IMREAD_COLOR)
        result = check_face_match(frame)
    except Exception as e:
        result = {"authorized": False, "message": f"Erro interno: {e}"}

    liberar_memoria()
    return jsonify(result)


@app.route("/", methods=["GET"])
def health_check():
    return jsonify({
        "status": "OK",
        "device": DEVICE,
        "users_loaded": len(usuarios),
        "models_ready": modelos_carregados
    })


# ======================================
# ✅ MAIN
# ======================================
if __name__ == "__main__":
    print("\n⚙️ Servidor Flask iniciado. Modelos serão carregados em background...")
    app.run(host="0.0.0.0", port=5000, debug=False)
