import face_recognition
import os
import numpy as np
import base64
import cv2


def cargar_rostros(db_path="caras_db"):
    rostros_conocidos = []
    nombres = []

    for archivo in os.listdir(db_path):
        if archivo.lower().endswith((".jpg", ".png", ".jpeg")):
            ruta = os.path.join(db_path, archivo)
            imagen = face_recognition.load_image_file(ruta)
            # También preprocessamos las imágenes de la DB para consistencia
            imagen_mejorada = mejorar_imagen(imagen[:, :, ::-1])  # RGB->BGR->mejorar->RGB
            encodings = face_recognition.face_encodings(imagen_mejorada)
            if not encodings:
                # Si el preprocesamiento no ayudó, intentamos con la original
                encodings = face_recognition.face_encodings(imagen)
            if encodings:
                rostros_conocidos.append(encodings[0])
                nombres.append(os.path.splitext(archivo)[0])

    return rostros_conocidos, nombres


def mejorar_imagen(frame_bgr):
    """
    Pipeline de mejora para condiciones de poca luz:
    1. Corrección gamma   → levanta sombras sin quemar blancos
    2. CLAHE              → mejora contraste local por zona del rostro
    3. Reducción de ruido → elimina granulosidad de poca luz
    Retorna la imagen en RGB lista para face_recognition.
    """
    # 1. Corrección gamma adaptativa
    media = np.mean(cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2GRAY))
    if media < 80:           # imagen oscura
        gamma = 0.5
    elif media < 120:        # algo oscura
        gamma = 0.7
    else:
        gamma = 1.0          # luz aceptable, sin cambios

    if gamma != 1.0:
        tabla = np.array([
            ((i / 255.0) ** gamma) * 255
            for i in range(256)
        ], dtype=np.uint8)
        frame_bgr = cv2.LUT(frame_bgr, tabla)

    # 2. CLAHE sobre canal L del espacio LAB
    lab = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2LAB)
    l, a, b = cv2.split(lab)
    clahe = cv2.createCLAHE(clipLimit=2.5, tileGridSize=(8, 8))
    l_eq = clahe.apply(l)
    lab_eq = cv2.merge([l_eq, a, b])
    frame_bgr = cv2.cvtColor(lab_eq, cv2.COLOR_LAB2BGR)

    # 3. Reducción de ruido (suave para no perder detalles faciales)
    frame_bgr = cv2.fastNlMeansDenoisingColored(frame_bgr, None, h=6, hColor=6,
                                                 templateWindowSize=7,
                                                 searchWindowSize=21)

    # Retornar en RGB para face_recognition
    return cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)


def reconocer_desde_imagen(rostros_conocidos, nombres, imagen_bytes, tolerancia=0.50):
    """
    Recibe bytes de una imagen del navegador.
    Aplica mejora de imagen antes de intentar el reconocimiento.
    Retorna (nombre, mensaje).
    """
    np_arr = np.frombuffer(imagen_bytes, np.uint8)
    frame_bgr = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)

    if frame_bgr is None:
        return None, "❌ No se pudo decodificar la imagen"

    # ── Intento 1: imagen mejorada ──────────────────────────────────────────
    rgb_mejorado = mejorar_imagen(frame_bgr)
    ubicaciones = face_recognition.face_locations(rgb_mejorado, model="hog")

    # ── Intento 2: sin mejora (por si la imagen ya era buena) ───────────────
    if not ubicaciones:
        rgb_original = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
        ubicaciones = face_recognition.face_locations(rgb_original, model="hog")
        rgb_mejorado = rgb_original  # usar original si fue el que encontró caras

    if not ubicaciones:
        return None, "⚠️ No se detectó ningún rostro — mejora la iluminación o acércate más"

    encodings = face_recognition.face_encodings(rgb_mejorado, ubicaciones)

    for encoding in encodings:
        distancias = face_recognition.face_distance(rostros_conocidos, encoding)
        if len(distancias) == 0:
            continue
        idx_mejor = int(np.argmin(distancias))
        distancia_mejor = distancias[idx_mejor]

        if distancia_mejor <= tolerancia:
            nombre = nombres[idx_mejor]
            confianza = round((1 - distancia_mejor) * 100, 1)
            return nombre, f"✅ Reconocido: {nombre} ({confianza}% confianza)"

    return None, "❌ Rostro no reconocido — intenta con mejor iluminación"


def reconocer_desde_imagen_fernandoi(rostros_conocidos, nombres, imagen_bytes, tolerancia=0.50, target_id=None):
    """
    Versión especializada para FernandoI con mejor tasa de reconocimiento.
    Retorna (nombre, mensaje, match_procentaje) donde match_procentaje es el
    porcentaje de confianza (0.0–100.0) o None si no hubo match.
    Si se provee target_id, valida estrictamente contra ese usuario en particular.
    """
    np_arr = np.frombuffer(imagen_bytes, np.uint8)
    frame_bgr = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)

    if frame_bgr is None:
        return None, "❌ Error de imagen (FernandoI)", None

    rgb_mejorado = mejorar_imagen(frame_bgr)
    ubicaciones = face_recognition.face_locations(rgb_mejorado, model="hog")

    if not ubicaciones:
        # Intento sin mejora
        rgb_original = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
        ubicaciones = face_recognition.face_locations(rgb_original, model="hog")
        rgb_mejorado = rgb_original

    if not ubicaciones:
        return None, "⚠️ No se detectó rostro en la cámara", None

    encodings = face_recognition.face_encodings(rgb_mejorado, ubicaciones)

    for encoding in encodings:
        if target_id is not None:
            # Buscar índices del target_id
            indices = [i for i, n in enumerate(nombres) if str(n) == str(target_id)]
            if not indices:
                return None, f"❌ No hay foto base registrada para el ID {target_id}", None
            
            rostros_target = [rostros_conocidos[i] for i in indices]
            distancias = face_recognition.face_distance(rostros_target, encoding)
            if len(distancias) == 0:
                continue
                
            distancia_mejor = min(distancias)
            confianza = round((1 - distancia_mejor) * 100, 1)

            # Para validación contra usuario autenticado (post-PIN):
            # aceptar el match si la confianza es > 1% (umbral relajado).
            if confianza > 1.0:
                return target_id, f"✅ Identidad verificada ({confianza}%)", confianza
            else:
                return None, f"❌ El rostro no coincide con tu usuario (Match: {confianza}%)", confianza
        else:
            # Lógica anterior de reconocimiento general
            distancias = face_recognition.face_distance(rostros_conocidos, encoding)
            if len(distancias) == 0:
                continue
                
            idx_mejor = int(np.argmin(distancias))
            distancia_mejor = distancias[idx_mejor]

            if distancia_mejor <= tolerancia:
                nombre = nombres[idx_mejor]
                confianza = round((1 - distancia_mejor) * 100, 1)
                return nombre, f"✅ Colaborador Reconocido: {nombre} ({confianza}%)", confianza

    return None, "❌ Rostro no identificado o no coincide con el usuario", None