"""
Homonimia v3 — usa EDT_homonimia 'OBTENER', <limit>.

El SP devuelve un resultset unificado con columnas:
    tabla | id | cuit | razon_social | fuente | dni | id_fide | id_tabla

Se procesan las filas agrupadas por id_tabla (una empresa de EDT_cuits_a_procesar).
Dentro de cada grupo: primero la sociedad (editos_ia), luego personas y vinculadas.
Al terminar cada grupo se llama a EDT_homonimia 'PROCESADO' que marca el avance
y dispara edictos_procesar_id_fide.

Uso:
    python procesamiento/homonimia/homonimia_v3.py [--limit N]
"""

import logging
import signal
import sys
import os
from contextlib import contextmanager

BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
sys.path.insert(0, BASE_DIR)

ROW_TIMEOUT = 30  # segundos máx por fila antes de considerarla trabada


@contextmanager
def _timeout(seconds, label):
    def _handler(_signum, _frame):
        raise TimeoutError(f"Timeout ({seconds}s) en {label}")
    old = signal.signal(signal.SIGALRM, _handler)
    signal.alarm(seconds)
    try:
        yield
    finally:
        signal.alarm(0)
        signal.signal(signal.SIGALRM, old)

from helpers.db import conectar, conectar_readonly, ejecutar_sp_raw, ejecutar_sp_dict
from helpers.logging_setup import setup_logging
from procesamiento.homonimia.homonimia_v2 import (
    buscar_y_resolver,
    resolver_via_dni,
    resolver_via_historicos,
    asignar,
    marcar_negativo,
    marcar_multiple,
)

# El SP devuelve 'editos_ia' (typo en la BD, sin 'c')
_TABLA_SOCIEDAD   = 'editos_ia'
_TABLA_SOCIEDAD_W = 'edictos_ia'   # nombre real para los SPs de escritura
_TABLA_PERSONAS   = 'sociedades_constituciones_relaciones_personas'
_TABLA_VINCULADAS = 'sociedades_constituciones_relaciones_vinculadas'


def _obtener_filas(conn_read, limit):
    return ejecutar_sp_dict('EDT_homonimia', ('OBTENER', limit), conn_read) or []


def _marcar_procesado(conn_write, id_tabla):
    ejecutar_sp_raw('EDT_homonimia', ('PROCESADO', None, id_tabla), conn_write)
    conn_write.commit()


def _agrupar(filas):
    """
    Agrupa filas por id_tabla manteniendo el orden de aparición del SP.
    Las filas dentro de cada grupo conservan el orden original.
    Retorna lista de (id_tabla, [filas_en_orden]).
    """
    orden = []
    grupos = {}
    for fila in filas:
        id_tabla = fila['id_tabla']
        if id_tabla not in grupos:
            orden.append(id_tabla)
            grupos[id_tabla] = []
        grupos[id_tabla].append(fila)
    return [(id_tabla, grupos[id_tabla]) for id_tabla in orden]


def _rellenar_id_padron_por_cuit(cuit, id_padron, conn_write):
    """Actualiza todos los edictos_ia con ese CUIT que aún tengan id_padron NULL."""
    rows = ejecutar_sp_dict('EDT_homonimia_relleno_cuit', (id_padron, cuit), conn_write) or []
    conn_write.commit()
    n = rows[0]['filas_actualizadas'] if rows else 0
    if n:
        logging.info(f"    RELLENO_CUIT | cuit={cuit} id_padron={id_padron} edictos_actualizados={n}")
    return n


def _procesar_sociedad(fila, conn_read, conn_write, stats):
    id_edicto = fila['id']
    id_fide   = fila['id_fide']
    cuit      = fila['cuit']
    nombre    = fila['razon_social']

    cuit_enc, id_padron, motivo, candidatos = buscar_y_resolver(conn_read, cuit, nombre, None)
    logging.info(f"    buscar_y_resolver → cuit_enc={cuit_enc} id_padron={id_padron} motivo={motivo} n_candidatos={len(candidatos)}")
    if candidatos:
        logging.info(f"    primer candidato raw: {candidatos[0]}")
    if cuit_enc:
        asignar(conn_write, cuit_enc, id_padron, id_edicto, _TABLA_SOCIEDAD_W)
        stats['asignado_directo' if motivo == 'único' else 'asignado_fuzzy'] += 1
        logging.info(f"    SOC ASIGNADA ({motivo}): {cuit_enc} id_padron={id_padron} id_fide={id_fide}")
        if id_padron:
            _rellenar_id_padron_por_cuit(cuit_enc, id_padron, conn_write)
        return cuit_enc, id_padron
    else:
        marcar_negativo(conn_write, id_edicto, _TABLA_SOCIEDAD_W)
        stats['sin_resultados'] += 1
        logging.info(f"    SOC SIN RESULTADO: {motivo} id_fide={id_fide}")
        return None, None


def _procesar_relacion(fila, tabla_w, cuit_soc, conn_read, conn_write, stats):
    id_edicto = fila['id']
    id_fide   = fila['id_fide']
    cuit      = fila['cuit']
    nombre    = fila['razon_social']
    dni       = fila['dni']

    if cuit:
        logging.info(f"    CUIT_FUENTE={cuit} id_fide={id_fide} — buscando en padrón por CUIT")

    cuit_enc, id_padron, motivo, candidatos = buscar_y_resolver(conn_read, cuit, nombre, None)
    logging.info(f"    buscar_y_resolver → cuit_enc={cuit_enc} id_padron={id_padron} motivo={motivo} n_candidatos={len(candidatos)}")
    if candidatos:
        logging.info(f"    primer candidato raw: {candidatos[0]}")

    if cuit_enc:
        asignar(conn_write, cuit_enc, id_padron, id_edicto, tabla_w)
        stats['asignado_directo' if motivo == 'único' else 'asignado_fuzzy'] += 1
        logging.info(f"    ASIGNADO ({motivo}): {cuit_enc} id_padron={id_padron} id_fide={id_fide}")
        return

    logging.info(f"    {motivo}")

    if dni:
        cuit_dni, id_padron_dni = resolver_via_dni(conn_read, nombre, dni)
        if cuit_dni:
            asignar(conn_write, cuit_dni, id_padron_dni, id_edicto, tabla_w)
            stats['asignado_fuzzy'] += 1
            logging.info(f"    ASIGNADO (dni): {cuit_dni} id_padron={id_padron_dni}")
            return

    if cuit_soc and candidatos:
        cuit_hist, id_padron_hist, motivo_hist = resolver_via_historicos(
            conn_read, candidatos, nombre, cuit_soc
        )
        if cuit_hist:
            asignar(conn_write, cuit_hist, id_padron_hist, id_edicto, tabla_w)
            stats['asignado_historico'] += 1
            logging.info(f"    ASIGNADO ({motivo_hist}): {cuit_hist} id_padron={id_padron_hist}")
            return
        else:
            logging.info(f"    Irresoluble via históricos: {motivo_hist}")

    if 'múltiple' in motivo:
        stats['multiple_sin_ganador'] += 1
        marcar_multiple(conn_write, id_edicto, tabla_w)
    else:
        stats['sin_resultados'] += 1
        marcar_negativo(conn_write, id_edicto, tabla_w)


def proceso(limit):
    logging.info(f"PROCESO_INICIADO | limit={limit}")

    conn_read  = conectar_readonly()
    conn_write = conectar()

    stats = {
        'asignado_directo':     0,
        'asignado_fuzzy':       0,
        'asignado_historico':   0,
        'multiple_sin_ganador': 0,
        'sin_resultados':       0,
        'error':                0,
    }

    filas = _obtener_filas(conn_read, limit)
    logging.info(f"Filas obtenidas: {len(filas)}")

    grupos = _agrupar(filas)
    logging.info(f"Empresas a procesar: {len(grupos)}")

    for id_tabla, filas_grupo in grupos:
        nombre_grupo = next((f['razon_social'] for f in filas_grupo if (f['tabla'] or '').strip() == _TABLA_SOCIEDAD), f"id_tabla={id_tabla}")
        logging.info(f"[EMPRESA] id_tabla={id_tabla} nombre='{nombre_grupo}' filas={len(filas_grupo)}")

        # Cache id_fide -> cuit resuelto para que las personas usen el CUIT de su sociedad padre
        cuits_por_fide = {}

        for fila in filas_grupo:
            tabla = (fila['tabla'] or '').strip()
            id_fide = fila['id_fide']

            if tabla == _TABLA_SOCIEDAD:
                logging.info(f"  [SOC] id={fila['id']} id_fide={id_fide} nombre='{fila['razon_social']}'")
                try:
                    with _timeout(ROW_TIMEOUT, f"SOC id={fila['id']}"):
                        cuit_resuelto, _ = _procesar_sociedad(fila, conn_read, conn_write, stats)
                    if cuit_resuelto:
                        cuits_por_fide[id_fide] = cuit_resuelto
                except Exception as e:
                    stats['error'] += 1
                    logging.error(f"  ERROR sociedad id={fila['id']} id_fide={id_fide}: {e}")
                    try:
                        conn_write.rollback()
                    except Exception:
                        pass

            elif tabla == _TABLA_PERSONAS:
                cuit_soc = cuits_por_fide.get(id_fide)
                logging.info(f"  [PER] id={fila['id']} id_fide={id_fide} nombre='{fila['razon_social']}'")
                try:
                    with _timeout(ROW_TIMEOUT, f"PER id={fila['id']}"):
                        _procesar_relacion(fila, _TABLA_PERSONAS, cuit_soc, conn_read, conn_write, stats)
                except Exception as e:
                    stats['error'] += 1
                    logging.error(f"  ERROR persona id={fila['id']} id_fide={id_fide}: {e}")
                    try:
                        conn_write.rollback()
                    except Exception:
                        pass

            elif tabla == _TABLA_VINCULADAS:
                cuit_soc = cuits_por_fide.get(id_fide)
                logging.info(f"  [VIN] id={fila['id']} id_fide={id_fide} nombre='{fila['razon_social']}'")
                try:
                    with _timeout(ROW_TIMEOUT, f"VIN id={fila['id']}"):
                        _procesar_relacion(fila, _TABLA_VINCULADAS, cuit_soc, conn_read, conn_write, stats)
                except Exception as e:
                    stats['error'] += 1
                    logging.error(f"  ERROR vinculada id={fila['id']} id_fide={id_fide}: {e}")
                    try:
                        conn_write.rollback()
                    except Exception:
                        pass

        # Marcar empresa como procesada (actualiza EDT_cuits_a_procesar y dispara edictos_procesar_id_fide)
        try:
            _marcar_procesado(conn_write, id_tabla)
            logging.info(f"  [PROCESADO] id_tabla={id_tabla}")
        except Exception as e:
            logging.error(f"  ERROR al marcar procesado id_tabla={id_tabla}: {e}")

    conn_read.close()
    conn_write.close()

    total     = len(filas)
    asignados = stats['asignado_directo'] + stats['asignado_fuzzy'] + stats['asignado_historico']
    logging.info(
        f"PROCESO_FINALIZADO | total_filas={total} empresas={len(grupos)} asignados={asignados} "
        f"directo={stats['asignado_directo']} fuzzy={stats['asignado_fuzzy']} "
        f"historico={stats['asignado_historico']} "
        f"multiple={stats['multiple_sin_ganador']} sin_resultado={stats['sin_resultados']} "
        f"errores={stats['error']}"
    )
    return stats


if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser(description="Homonimia v3 — SP unificado EDT_homonimia")
    parser.add_argument(
        '--limit', type=int, default=1,
        help="Cantidad de empresas a procesar (default: 1)"
    )
    args = parser.parse_args()

    setup_logging("homonimia_v3")
    proceso(args.limit)
