import os
import cv2
import re
import numpy as np
import time
import glob
from paddleocr import PaddleOCR


CARPETA_PENDIENTES = "pendientes"
CARPETA_RESULTADOS = "resultados"
CARPETA_PROCESADOS = "procesados"

for carpeta in [CARPETA_PENDIENTES, CARPETA_RESULTADOS, CARPETA_PROCESADOS]:
    os.makedirs(carpeta, exist_ok=True)

os.environ['FLAGS_enable_pir_api'] = '0'
os.environ['PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK'] = 'True'

print("Cargando Motores PaddleOCR")
ocr_engine = PaddleOCR(lang='es', use_angle_cls=True, show_log=False)

ESTADOS_MEXICO = {
    "01": "AGUASCALIENTES", "02": "BAJA CALIFORNIA", "03": "BAJA CALIFORNIA SUR",
    "04": "CAMPECHE", "05": "COAHUILA", "06": "COLIMA", "07": "CHIAPAS",
    "08": "CHIHUAHUA", "09": "CDMX", "10": "DURANGO", "11": "GUANAJUATO",
    "12": "GUERRERO", "13": "HIDALGO", "14": "JALISCO", "15": "MEXICO",
    "16": "MICHOACAN", "17": "MORELOS", "18": "NAYARIT", "19": "NUEVO LEON",
    "20": "OAXACA", "21": "PUEBLA", "22": "QUERETARO", "23": "QUINTANA ROO",
    "24": "SAN LUIS POTOSI", "25": "SINALOA", "26": "SONORA", "27": "TABASCO",
    "28": "TAMAULIPAS", "29": "TLAXCALA", "30": "VERACRUZ", "31": "YUCATAN", "32": "ZACATECAS"
}

# --- 1. NUEVA FUNCIÓN DE NITIDEZ ---
def medir_nitidez(ruta):
    img = cv2.imread(ruta)
    if img is None: return 0
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    return cv2.Laplacian(gray, cv2.CV_64F).var()

def detectar_pantalla_gabor(ruta_imagen, umbral=1000):
    img = cv2.imread(ruta_imagen, cv2.IMREAD_GRAYSCALE)
    if img is None: return False, 0
    img = cv2.resize(img, (500, 500))
    
    ksize, sigma, theta, lambd, gamma = 31, 4.0, np.pi/4, 10.0, 0.5
    kernel = cv2.getGaborKernel((ksize, ksize), sigma, theta, lambd, gamma, 0, ktype=cv2.CV_32F)
    filtrada = cv2.filter2D(img, cv2.CV_8UC3, kernel)
    score = np.var(filtrada)
    return score > umbral, score

# --- FUNCIONES Validacion del frente ---

def validar_coherencia(curp, clave):
    if curp == "No encontrada" or clave == "No encontrada":
        return False
    return curp[4:10] == clave[6:12]

def extraer_datos_avanzado(lista_textos):
    texto_completo = " ".join(lista_textos).upper().replace(" ", "")
    texto_limpio = texto_completo.replace("CLAVEDEELECTOR", "").replace("CLAVEDEELECTO", "")
    curp_pattern = r'[A-Z]{4}\d{6}[HM][A-Z]{5}[A-Z\d]\d'
    clave_pattern = r'[A-Z\d]{6}\d{6}[A-Z\d]{2}[A-Z\d]\d{3}'
    res_curp = "No encontrada"
    res_clave = "No encontrada"
    m_curp = re.search(curp_pattern, texto_limpio)
    if m_curp: res_curp = m_curp.group()
    m_clave = re.search(clave_pattern, texto_limpio)
    if m_clave: res_clave = m_clave.group()
    return res_curp, res_clave

def procesar_ine_frente(ruta_imagen):
    try:
        result = ocr_engine.ocr(ruta_imagen, cls=True)
        textos = [linea[1][0] for linea in result[0]] if result[0] else []
        curp, clave = extraer_datos_avanzado(textos)
        
        if curp == "No encontrada" or clave == "No encontrada":
            img_cv = cv2.imread(ruta_imagen)
            gray = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY)
            thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
            result_2 = ocr_engine.ocr(thresh, cls=True)
            textos_2 = [linea[1][0] for linea in result_2[0]] if result_2[0] else []
            textos.extend(textos_2)
            c2, k2 = extraer_datos_avanzado(textos_2)
            if curp == "No encontrada": curp = c2
            if clave == "No encontrada": clave = k2

        coherente = validar_coherencia(curp, clave)
        anio_actual = 2026
        es_vigente = False
        anio_vencimiento = None
        texto_unido = " ".join(textos).upper()
        anios_detectados = re.findall(r'20[23]\d', texto_unido)
        
        if anios_detectados:
            anios_int = [int(a) for a in anios_detectados]
            anio_vencimiento = max(anios_int)
            if anio_vencimiento >= anio_actual:
                es_vigente = True
        
        nombre_estado = "No identificado"
        if clave != "No encontrada":
            cod_estado = clave[12:14]
            nombre_estado = ESTADOS_MEXICO.get(cod_estado, "Desconocido")

        img_face = cv2.imread(ruta_imagen)
        gray_face = cv2.cvtColor(img_face, cv2.COLOR_BGR2GRAY)
        gray_face = cv2.equalizeHist(gray_face)
        face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        faces = face_cascade.detectMultiScale(gray_face, scaleFactor=1.05, minNeighbors=3, minSize=(40, 40))
        tiene_rostro = len(faces) > 0

        valido_frente = (curp != "No encontrada" and tiene_rostro and es_vigente)

        return {
            "curp": curp, "clave_elector": clave, "coinciden_fechas": coherente,
            "vigente": es_vigente, "anio_vencimiento": anio_vencimiento,
            "estado": nombre_estado, "tiene_rostro": tiene_rostro, "valido_frente": valido_frente
        }
    except Exception as e:
        return {"error": str(e)}



def decodificar_mrz_oficial(lista_textos):
    bloque_limpio = "<<".join(lista_textos).upper().replace(" ", "")
    idmex_match = re.search(r'IDMEX(\d{10,12})', bloque_limpio)
    if not idmex_match:
        idmex_match = re.search(r'(\d{10,12})', bloque_limpio)
    id_credencial = idmex_match.group(1) if idmex_match else "No detectado"

    patron = re.search(r'(\d{6})\d([HM])(\d{6})\d', bloque_limpio)
    nacimiento = "No detectado"; sexo = "No detectado"; vencimiento = "No detectado"
    nacimiento_raw = None

    if patron:
        f_nac = patron.group(1)
        nacimiento_raw = f_nac 
        yy, mm, dd = f_nac[0:2], f_nac[2:4], f_nac[4:6]
        siglo = "19" if int(yy) > 30 else "20"
        nacimiento = f"{dd}/{mm}/{siglo}{yy}"
        sexo = "HOMBRE" if patron.group(2) == "H" else "MUJER"
        f_ven = patron.group(3)
        v_yy, v_mm, v_dd = f_ven[0:2], f_ven[2:4], f_ven[4:6]
        vencimiento = f"20{v_yy}-{v_mm}-{v_dd}"

    return {
        "id_credencial": id_credencial,
        "nacimiento": nacimiento,
        "nacimiento_raw": nacimiento_raw,
        "sexo": sexo,
        "vencimiento": vencimiento,
        "valido_atras": id_credencial != "No detectado" and nacimiento != "No detectado"
    }

def procesar_reverso_ine(ruta):
    try:
        result = ocr_engine.ocr(ruta, cls=True)
        if result and result[0]:
            textos = [linea[1][0] for linea in result[0]]
            return decodificar_mrz_oficial(textos)
        
        img = cv2.imread(ruta)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        result = ocr_engine.ocr(thresh, cls=True)
        
        if result and result[0]:
            textos = [linea[1][0] for linea in result[0]]
            return decodificar_mrz_oficial(textos)
        return {"error": "No se pudo extraer texto"}
    except Exception as e:
        return {"error": str(e)}

# --- LÓGICA DE INTEGRACIÓN Y VALIDACIÓN CRUZADA ---

if __name__ == "__main__":
    print("Monitor INE Iniciado. Nombra tus archivos como 'persona_frente.jpg' y 'persona_atras.jpg'")
    
    while True:
        archivos = glob.glob(os.path.join(CARPETA_PENDIENTES, "*.*"))
        grupos = {}
        for r in archivos:
            base = "_".join(os.path.basename(r).split('_')[:-1])
            if base not in grupos: grupos[base] = []
            grupos[base].append(r)

        for sujeto, rutas in grupos.items():
            if len(rutas) < 2: continue 
            
            print(f"\n>>> Procesando Identidad Completa: {sujeto}")
            
            # --- 2. EVALUAR NITIDEZ DE AMBOS LADOS ---
            alertas_calidad = []
            umbral_nitidez = 80.0
            data_frente = {}; data_atras = {}
            f_path = ""; a_path = ""

            for r in rutas:
                # --- NUEVA VALIDACIÓN DE PANTALLA ---
                es_pantalla, score_p = detectar_pantalla_gabor(r, umbral=1000)
                if es_pantalla:
                    tipo = "FRENTE" if "frente" in r.lower() else "ATRÁS"
                    alertas_calidad.append(f"FRAUDE: {tipo} es una PANTALLA (Score: {score_p:.1f})")

                valor_nitidez = medir_nitidez(r)
                if valor_nitidez < umbral_nitidez:
                    tipo = "FRENTE" if "frente" in r.lower() else "ATRÁS"
                    alertas_calidad.append(f"{tipo} está borrosa ({valor_nitidez:.1f})")

                if "frente" in r.lower():
                    data_frente = procesar_ine_frente(r)
                    f_path = r
                elif "atras" in r.lower() or "reverso" in r.lower():
                    data_atras = procesar_reverso_ine(r)
                    a_path = r

            # VALIDACIÓN CRUZADA
            coherencia_global = False
            obs_cruzada = "No se pudo realizar validación cruzada"
            
            if "curp" in data_frente and "nacimiento_raw" in data_atras:
                curp = data_frente["curp"]
                nac_mrz = data_atras["nacimiento_raw"]
                match_nac = curp[4:10] == nac_mrz if curp != "No encontrada" and nac_mrz else False
                sexo_f = "H" if data_atras["sexo"] == "HOMBRE" else "M"
                match_sexo = curp[10] == sexo_f if curp != "No encontrada" and len(curp)>10 else False
                
                if match_nac and match_sexo:
                    coherencia_global = True
                    obs_cruzada = "Coherencia total entre Frente y Vuelta ✅"
                else:
                    obs_cruzada = "ERROR: Los datos del frente no coinciden con el reverso ❌"

            # RESULTADO FINAL
            hay_fraude = any("FRAUDE" in alerta for alerta in alertas_calidad)
            
            ine_valida_final = data_frente.get("valido_frente", False) and \
                               data_atras.get("valido_atras", False) and \
                               coherencia_global and not hay_fraude

            # GUARDAR REPORTE UNIFICADO
            ruta_resultado = os.path.join(CARPETA_RESULTADOS, f"REPORTE_{sujeto}.txt")
            with open(ruta_resultado, "w", encoding="utf-8") as f:
                f.write(f"REPORTE DE VALIDACIÓN INE: {sujeto.upper()}\n")
                f.write("="*45 + "\n")
                
                # Reportar alertas de calidad si existen
                if alertas_calidad:
                    f.write("⚠️ ALERTAS DE CALIDAD:\n")
                    for al in alertas_calidad:
                        f.write(f"  - {al}\n")
                    f.write("-" * 45 + "\n")

                f.write(f"RESULTADO FINAL: {'IDENTIFICACIÓN VÁLIDA ✅' if ine_valida_final else 'IDENTIFICACIÓN FALSA O INVÁLIDA ❌'}\n")
                f.write(f"COHERENCIA CRUZADA: {obs_cruzada}\n")
                f.write("="*45 + "\n\n")
                
                f.write("APARTADO A: ANVERSO (FRENTE)\n")
                f.write("-" * 30 + "\n")
                for k, v in data_frente.items(): f.write(f"{k}: {v}\n")
                
                f.write("\nAPARTADO B: REVERSO (ATRÁS)\n")
                f.write("-" * 30 + "\n")
                for k, v in data_atras.items(): f.write(f"{k}: {v}\n")

            # MOVER ARCHIVOS
            for r in [f_path, a_path]:
                if r: os.rename(r, os.path.join(CARPETA_PROCESADOS, os.path.basename(r)))
            
            print(f"Finalizado: Reporte generado en {CARPETA_RESULTADOS}")

        time.sleep(2)