import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = "rtsp_transport;tcp|stimeout;3000000"

# ⚡ NUEVOS CANDADOS: Silenciamos la burocracia de OpenCV y Qt
os.environ['QT_LOGGING_RULES'] = '*=false'
os.environ['OPENCV_LOG_LEVEL'] = 'ERROR'
import cv2
import numpy as np
import time
import threading
from queue import Queue
from ultralytics import YOLO
import warnings
import json

warnings.filterwarnings("ignore")

# ──────────────────────────────────────────────────────────────────────────────
#  1. IMPORTAMOS NUESTROS MÓDULOS 
# ──────────────────────────────────────────────────────────────────────────────
# Del motor matemático y tracking
from seguimiento2 import GlobalMemory, CamManager, SECUENCIA, URLS, FUENTE, similitud_hibrida

# ⚡ Del motor de reconocimiento facial
from reconocimiento import (
    app,  
    gestionar_vectores,
    buscar_mejor_match,
    hilo_bienvenida,
    UMBRAL_SIM,
    COOLDOWN_TIME
)

# ──────────────────────────────────────────────────────────────────────────────
#  2. PROTECCIONES MULTIHILO E INICIALIZACIÓN
# ──────────────────────────────────────────────────────────────────────────────
COLA_ROSTROS = Queue(maxsize=4) 
IA_LOCK = threading.Lock() 

print("\nIniciando carga de base de datos...")
BASE_DATOS_ROSTROS = gestionar_vectores(actualizar=True)

# ──────────────────────────────────────────────────────────────────────────────
#  3. MOTOR ASÍNCRONO CON INSIGHTFACE
# ──────────────────────────────────────────────────────────────────────────────
def procesar_rostro_async(roi_cabeza, gid, cam_id, global_mem, trk):
    try:
        if not BASE_DATOS_ROSTROS or roi_cabeza.size == 0: return

        with IA_LOCK:
            faces = app.get(roi_cabeza)

        if len(faces) == 0:
            return

        face = max(faces, key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1]))

        w_rostro = face.bbox[2] - face.bbox[0]
        h_rostro = face.bbox[3] - face.bbox[1]

        # ⚡ Límite bajo (20px) para reconocer desde más lejos
        if w_rostro < 20 or h_rostro < 20 or (w_rostro / h_rostro) < 0.35:
            return

        emb = np.array(face.normed_embedding, dtype=np.float32)
        genero_detectado = "Man" if face.sex == "M" else "Woman"

        mejor_match, max_sim = buscar_mejor_match(emb, BASE_DATOS_ROSTROS)
        print(f"[DEBUG CAM {cam_id}] InsightFace: {mejor_match} al {max_sim:.2f}")

        votos_finales = 0 

        # ──────────────────────────────────────────────────────────────────
        # ⚡ LÓGICA DE VOTACIÓN PONDERADA (Con tus umbrales)
        # ──────────────────────────────────────────────────────────────────
        if max_sim >= 0.18 and mejor_match:
            nombre_limpio = mejor_match.split('_')[0]
            id_definitivo = gid 

            with global_mem.lock:
                datos_id = global_mem.db.get(gid)
                if not datos_id: return
                
                dueno_actual = datos_id.get('nombre')
                candidato_actual = datos_id.get('candidato_nombre')

                # ⚡ REBELIÓN DE IDENTIDAD: 
                if max_sim >= 0.56 and dueno_actual is not None and dueno_actual != nombre_limpio:
                    print(f"[REBELIÓN] Cara de {nombre_limpio} clarísima ({max_sim:.2f}). Destronando a {dueno_actual}.")
                    datos_id['candidato_nombre'] = nombre_limpio
                    datos_id['votos_nombre'] = 3 
                else:
                    if candidato_actual == nombre_limpio:
                        datos_id['votos_nombre'] = datos_id.get('votos_nombre', 0) + 1
                    else:
                        datos_id['candidato_nombre'] = nombre_limpio
                        datos_id['votos_nombre'] = 1

                    if max_sim >= 0.50: 
                        datos_id['votos_nombre'] += 3 
                    elif max_sim >= 0.40:
                        datos_id['votos_nombre'] += 2 

                votos_finales = datos_id['votos_nombre'] 

                # ⚡ Exigimos 3 votos
                if votos_finales >= 3:
                    # 1. Buscamos al veterano
                    id_veterano = None
                    for gid_mem, data_mem in global_mem.db.items():
                        if data_mem.get('nombre') == nombre_limpio and gid_mem != gid:
                            id_veterano = gid_mem
                            break
                            
                    # 2. FUSIÓN MÁGICA
                    if id_veterano is not None:
                        print(f" [FUSIÓN FACIAL] ID {gid} es clon. Fusionando con veterano ID {id_veterano} ({nombre_limpio}).")
                        global_mem.db[id_veterano]['firmas'].extend(datos_id.get('firmas', []))
                        if len(global_mem.db[id_veterano]['firmas']) > 15:
                            global_mem.db[id_veterano]['firmas'] = global_mem.db[id_veterano]['firmas'][-15:]
                            
                        datos_id['firmas'] = []
                        datos_id['fusionado_con'] = id_veterano
                        trk.gid = id_veterano
                        id_definitivo = id_veterano
                        
                    # 3. BAUTIZO NORMAL O CORRECCIÓN
                    else:
                        if dueno_actual is not None and dueno_actual != nombre_limpio:
                            if max_sim < 0.56:
                                print(f"[RECHAZO VIP] {dueno_actual} protegido de {nombre_limpio} ({max_sim:.2f})")
                                return 
                            else:
                                print(f" [CORRECCIÓN VIP] Renombrando a {nombre_limpio} ({max_sim:.2f})")

                        if dueno_actual != nombre_limpio:
                            datos_id['nombre'] = nombre_limpio
                            print(f"[BAUTIZO] ID {gid} confirmado como {nombre_limpio}")
                            
                    global_mem.db[id_definitivo]['ts'] = time.time()
                    
                    # ──────────────────────────────────────────────────────────────────
                    # 🔊 AUDIO DE BIENVENIDA (DISPARADOR CORREGIDO)
                    # ──────────────────────────────────────────────────────────────────
                    # Verificamos si es la cámara 7 y si la persona actual ya tiene nombre confirmado
                    if str(cam_id) == "7" and nombre_limpio:
                        ahora = time.time()
                        TIEMPO_COOLDOWN = 60 # 1 minuto para pruebas
                        
                        # Revisamos el historial de saludos en la memoria global (que ya carga el JSON)
                        info_saludo = global_mem.ultimos_saludos.get(nombre_limpio, {})
                        ultimo_ts = info_saludo.get('timestamp', 0) if isinstance(info_saludo, dict) else 0

                        if (ahora - ultimo_ts) > TIEMPO_COOLDOWN:
                            # 1. Bloqueamos inmediatamente para evitar repeticiones
                            global_mem.guardar_saludo(nombre_limpio)
                            
                            # 2. Determinar género
                            genero_oficial = genero_detectado
                            try:
                                import json, os
                                ruta_gen = os.path.join("cache_nombres", "generos.json")
                                if os.path.exists(ruta_gen):
                                    with open(ruta_gen, 'r') as f:
                                        dic_gen = json.load(f)
                                        if nombre_limpio in dic_gen:
                                            genero_oficial = dic_gen[nombre_limpio]
                            except Exception: pass
                            
                            print(f"📢 [AUDIO] {nombre_limpio} reconocido en Cam 7. Saludando...")
                            import threading
                            threading.Thread(target=hilo_bienvenida, args=(nombre_limpio, genero_oficial), daemon=True).start()
                        else:
                            # Opcional: print de debug para saber que el cooldown bloqueó el audio
                            pass
            
            # Blindaje OSNet
            if max_sim > 0.50 and votos_finales >= 3:
                global_mem.confirmar_firma_vip(id_definitivo, time.time())

    except Exception as e:
        print(f" Error en InsightFace asíncrono: {e}")
    finally:
        trk.procesando_rostro = False

def worker_rostros(global_mem):
    while True:
        roi_cabeza, gid, cam_id, trk = COLA_ROSTROS.get()
        procesar_rostro_async(roi_cabeza, gid, cam_id, global_mem, trk)
        COLA_ROSTROS.task_done()

# ──────────────────────────────────────────────────────────────────────────────
#  4. LOOP PRINCIPAL DE FUSIÓN 
# ──────────────────────────────────────────────────────────────────────────────
class CamStream:
    def __init__(self, url):
        self.url = url
        self.cap = cv2.VideoCapture(url)
        self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
        self.frame = None
        threading.Thread(target=self._run, daemon=True).start()
        
    def _run(self):
        while True:
            ret, f = self.cap.read()
            if ret: 
                self.frame = f
                time.sleep(0.01) 
            else: 
                time.sleep(2)
                self.cap.open(self.url)

def dibujar_track_fusion(frame_show, trk, global_mem):
    try: x1, y1, x2, y2 = map(int, trk.box)
    except Exception: return
    
    nombre_str = ""
    if trk.gid is not None:
        with global_mem.lock:
            nombre = global_mem.db.get(trk.gid, {}).get('nombre')
            if nombre: nombre_str = f" [{nombre}]"

    if trk.gid is None: color, label = (150, 150, 150), f"?{trk.local_id}"        
    elif nombre_str: color, label = (255, 0, 255), f"ID:{trk.gid}{nombre_str}" 
    elif getattr(trk, 'en_grupo', False): color, label = (0, 0, 255), f"ID:{trk.gid} [grp]"
    elif getattr(trk, 'aprendiendo', False): color, label = (255, 255, 0), f"ID:{trk.gid} [++]" 
    elif getattr(trk, 'origen_global', False): color, label = (0, 165, 255), f"ID:{trk.gid} [re-id]"
    else: color, label = (0, 255, 0), f"ID:{trk.gid}"
    
    cv2.rectangle(frame_show, (x1, y1), (x2, y2), color, 2)
    (tw, th), _ = cv2.getTextSize(label, FUENTE, 0.55, 1)
    cv2.rectangle(frame_show, (x1, y1-th-6), (x1+tw+2, y1), color, -1)
    cv2.putText(frame_show, label, (x1+1, y1-4), FUENTE, 0.55, (0,0,0), 1)

def main():
    print("\nIniciando Sistema")
    model = YOLO("yolov8n.pt")
    global_mem = GlobalMemory()
    managers = {str(c): CamManager(c, global_mem) for c in SECUENCIA}
    cams = [CamStream(u) for u in URLS]
    
    threading.Thread(target=worker_rostros, args=(global_mem,), daemon=True).start()

    cv2.namedWindow("SmartSoft", cv2.WINDOW_AUTOSIZE)
    idx = 0
    
    while True:
        now = time.time()
        tiles = []
        cam_ia = idx % len(cams)
        
        for i, cam_obj in enumerate(cams):
            frame = cam_obj.frame; cid = str(SECUENCIA[i])
            if frame is None: 
                tiles.append(np.zeros((270, 480, 3), np.uint8))
                continue
                
            frame_show = cv2.resize(frame.copy(), (480, 270))
            boxes = []
            turno_activo = (i == cam_ia)
            
            if turno_activo:
                # ⚡ 1. Subimos la confianza de YOLO de 0.50 a 0.60 para matar siluetas dudosas
                res = model.predict(frame_show, conf=0.60, iou=0.50, classes=[0], verbose=False, imgsz=480)
                
                if res[0].boxes: 
                    cajas_crudas = res[0].boxes.xyxy.cpu().numpy().tolist()
                    
                    # ⚡ 2. FILTRO BIOMECÁNICO (Anti-Sillas)
                    for b in cajas_crudas:
                        w_caja = b[2] - b[0]
                        h_caja = b[3] - b[1]
                        
                        # Un humano real es al menos 10% más alto que ancho (ratio > 1.1)
                        # Si la caja es muy cuadrada o ancha, la ignoramos y no se la pasamos a Kalman
                        if h_caja > (w_caja * 1.1):
                            boxes.append(b)
                    
            # ⚡ UPDATE LIMPIO (Sin IDs Globales)
            tracks = managers[cid].update(boxes, frame_show, frame, now, turno_activo)
            
            for trk in tracks:
                if getattr(trk, 'time_since_update', 1) <= 2: 
                    dibujar_track_fusion(frame_show, trk, global_mem)
                    
                    if turno_activo and trk.gid is not None and not getattr(trk, 'procesando_rostro', False):
                        
                        with global_mem.lock:
                            votos_actuales = global_mem.db.get(trk.gid, {}).get('votos_nombre', 0)
                        if votos_actuales >= 2:
                            continue 
                            
                        x1, y1, x2, y2 = trk.box
                        h_real, w_real = frame.shape[:2]
                        escala_x = w_real / 480.0
                        escala_y = h_real / 270.0
                        h_box = y2 - y1
                        
                        y_exp = max(0, y1 - h_box * 0.15)
                        y_cab = min(270, y1 + h_box * 0.50)
                        
                        roi_recortado = frame[
                            int(y_exp * escala_y) : int(y_cab * escala_y),
                            int(max(0, x1) * escala_x) : int(min(480, x2) * escala_x)
                        ].copy()

                        # ⚡ COMPUERTA ÓPTICA (Filtro Anti-Tartamudeo 25x25)
                        if roi_recortado.size > 0 and roi_recortado.shape[0] >= 25 and roi_recortado.shape[1] >= 25:
                            gray = cv2.cvtColor(roi_recortado, cv2.COLOR_BGR2GRAY)
                            nitidez = cv2.Laplacian(gray, cv2.CV_64F).var()
                            
                            # ⚡ BAJAMOS DE 12.0 a 5.0. 
                            # Deja pasar rostros un poco borrosos por la velocidad de caminar, 
                            # pero sigue bloqueando espaldas lisas.
                            if nitidez > 8.0:
                                if not COLA_ROSTROS.full():
                                    ultimo_envio = getattr(trk, 'ultimo_rostro_enviado', 0)
        
                                    # Solo mandamos la cara si ha pasado medio segundo desde la última vez que lo intentamos
                                    if (now - ultimo_envio) > 0.5:
                                        
                                        # Tu escudo anti-lag previo
                                        if COLA_ROSTROS.qsize() < 2:
                                            trk.ultimo_rostro_enviado = now # Registramos la hora del envío
                                            trk.procesando_rostro = True    # Bloqueamos el tracker
                                            
                                            # Aquí pones TU línea original de la cola:
                                            COLA_ROSTROS.put((roi_recortado, trk.gid, cid, trk), block=False)
                            else:
                                trk.ultimo_intento_cara = time.time() - 0.5

                            """if nitidez > 8.0:

                                if not COLA_ROSTROS.full():

                                    try:

                                        if COLA_ROSTROS.qsize() < 2:

                                            COLA_ROSTROS.put((roi_recortado, trk.gid, cid, trk), block=False)

                                            trk.procesando_rostro = True

                                            trk.ultimo_intento_cara = time.time()

                                    except queue.Full:

                                        pass

                            else:

                                trk.ultimo_intento_cara = time.time() - 0.5"""

            if turno_activo: cv2.circle(frame_show, (460, 20), 6, (0, 0, 255), -1)
            
            con_id = sum(1 for t in tracks if t.gid and getattr(t, 'time_since_update', 1) == 0)
            cv2.putText(frame_show, f"CAM {cid} [{con_id} ID]", (10, 28), FUENTE, 0.7, (255, 255, 255), 2)
            tiles.append(frame_show)
            
        if len(tiles) == 6: 
            cv2.imshow("SmartSoft Fusion", np.vstack([np.hstack(tiles[0:3]), np.hstack(tiles[3:6])]))
        
        idx += 1
        key = cv2.waitKey(1) & 0xFF 
        
        if key == ord('q'): 
            break
            
        elif key == ord('r'):
            print("\n[MODO REGISTRO] Escaneando mosaico para registrar...")
            mejor_roi = None
            max_area = 0
            face_metadata = None
            
            for i, cam_obj in enumerate(cams):
                if cam_obj.frame is None: continue
                
                faces = app.get(cam_obj.frame)
                for face in faces:
                    fw = face.bbox[2] - face.bbox[0]
                    fh = face.bbox[3] - face.bbox[1]
                    area = fw * fh
                    if area > max_area:
                        max_area = area
                        h_frame, w_frame = cam_obj.frame.shape[:2]
                        
                        m_x, m_y = int(fw * 0.30), int(fh * 0.30)
                        y1 = max(0, int(face.bbox[1]) - m_y)
                        y2 = min(h_frame, int(face.bbox[3]) + m_y)
                        x1 = max(0, int(face.bbox[0]) - m_x)
                        x2 = min(w_frame, int(face.bbox[2]) + m_x)
                        
                        mejor_roi = cam_obj.frame[y1:y2, x1:x2]
                        face_metadata = face
            
            if mejor_roi is not None and mejor_roi.size > 0:
                cv2.imshow("Nueva Persona", mejor_roi)
                cv2.waitKey(1)
                
                nom = input("Escribe el nombre de la persona: ").strip()
                cv2.destroyWindow("Nueva Persona")
                
                if nom:
                    import json
                    genero_guardado = "Man" if face_metadata.sex == "M" else "Woman"
                    
                    ruta_generos = os.path.join("cache_nombres", "generos.json")
                    os.makedirs("cache_nombres", exist_ok=True)
                    dic_generos = {}
                    
                    if os.path.exists(ruta_generos):
                        try:
                            with open(ruta_generos, 'r') as f:
                                dic_generos = json.load(f)
                        except Exception: pass
                            
                    dic_generos[nom] = genero_guardado
                    with open(ruta_generos, 'w') as f:
                        json.dump(dic_generos, f)

                    ruta_db = "db_institucion"
                    os.makedirs(ruta_db, exist_ok=True)
                    cv2.imwrite(os.path.join(ruta_db, f"{nom}.jpg"), mejor_roi)
                    
                    print(f"[OK] Rostro guardado (Género autodetectado: {genero_guardado})")
                    print(" Sincronizando base de datos en caliente...")
                    
                    nuevos_vectores = gestionar_vectores(actualizar=True)
                    with IA_LOCK:
                        BASE_DATOS_ROSTROS.clear() 
                        BASE_DATOS_ROSTROS.update(nuevos_vectores)
                    print(" Sistema listo.")
                else:
                    print("[!] Registro cancelado.")

if __name__ == "__main__":
    main()