from helpers.provincias import PROVINCIAS_CON_NUMERO
import difflib
import json
import logging
import unicodedata
import re

def buscar_cuit_sugerido(cuit, razon_social, provincia, conn_read):
    """
    Ejecuta Regla1 y Regla2 en orden para intentar obtener el CUIT sugerido.
    Si Regla1 no resuelve, Regla2 busca por razon_social independientemente
    de si había un CUIT original (que resultó inútil).
    """
    sugerido = Regla1(cuit, conn_read)
    if sugerido:
        return sugerido

    # Pasamos None como cuit para que Regla2 no quede bloqueada por la guarda
    # que exige cuit ausente: si llegamos acá, el cuit original no fue suficiente.
    sugerido = Regla2(None, razon_social, provincia, conn_read)
    return sugerido

def _exec(conn, sql, *params):
    c = conn.cursor()
    try:
        c.execute(sql, *params)
        return c.fetchall()
    finally:
        c.close()


def ejecutar_busqueda_cuit(conn_read, cuit=None, cod_provincia=None):
    try:
        return _exec(
            conn_read,
            'EXEC [dbo].[buscador_paises_padron] ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?',
            ('masivos', None, cuit, 'AR', cod_provincia, None, None, None, None, None, None, None)
        )
    except Exception as e:
        logging.error(f"Error al buscar por CUIT cuit={cuit}: {e}")
        return None

def compactar_siglas(texto):
    texto = re.sub(r'\bS[\s\.]*A[\s\.]*S\b', 'SAS', texto, flags=re.IGNORECASE)
    texto = re.sub(r'\bS[\s\.]*R[\s\.]*L\b', 'SRL', texto, flags=re.IGNORECASE)
    texto = re.sub(r'\bS[\s\.]*A\b', 'SA', texto, flags=re.IGNORECASE)
    return texto

def ejecutar_busqueda_rs(conn_read, razon_social=None, cod_provincia=None):
    try:
        razon_social = compactar_siglas(razon_social)
        return _exec(
            conn_read,
            'EXEC [dbo].[buscador_paises_padron] ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?',
            ('masivos', None, razon_social, 'AR', cod_provincia, None, None, None, None, None, None, None)
        )
    except Exception as e:
        logging.error(f"Error al buscar por razon_social='{razon_social}': {e}")
        return None

def ejecutar_busqueda_prov(conn_read, razon_social=None, cod_provincia=None):
    try:
        return _exec(
            conn_read,
            'EXEC [dbo].[buscador_paises_padron] ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?',
            ('buscador', None, razon_social, 'AR', cod_provincia, None, None, None, None, None, None, None)
        )
    except Exception as e:
        logging.error(f"Error al buscar por provincia razon_social='{razon_social}' cod_provincia={cod_provincia}: {e}")
        return None

def Regla1(cuit, conn_read):
    if cuit and cuit.lower() != 'null':
        cuit = purgar_cuit(cuit)
        resultados = ejecutar_busqueda_cuit(conn_read, cuit=cuit) or []
        if len(resultados) == 1:
            logging.debug(f"Regla1: CUIT único encontrado: {cuit}")
            return cuit
        else:
            logging.debug(f"Regla1: sin resultado único para cuit={cuit} (encontrados: {len(resultados)})")
            return None
    else:
        return None


def Regla2(cuit, razon_social, provincia, conn_read):
    if (not cuit or str(cuit).lower() == 'null') and razon_social and razon_social.lower() != 'null':
        razon_social_limpia = _normalizar_razon_social(razon_social)

        try:
            resultados = ejecutar_busqueda_rs(conn_read, razon_social=razon_social_limpia) or []
        except Exception as e:
            logging.error(f"Regla2: error en busqueda_rs razon_social='{razon_social}': {e}")
            return None

        if not resultados:
            logging.debug(f"Regla2: sin resultados para razon_social='{razon_social}'")
            return None

        try:
            resultados = [(r[3], r[4]) for r in resultados]
        except Exception:
            logging.error(f"Regla2: estructura inesperada en resultados para razon_social='{razon_social}'")
            return None

        if len(resultados) == 1:
            cuit_result = resultados[0][0]
            logging.debug(f"Regla2: RS única sugiere {cuit_result}")
            return cuit_result

        coincidencias_exactas = [
            r for r in resultados
            if purgar_nombre(purgar_espacios(razon_social)) == purgar_nombre(purgar_espacios(r[1]))
        ]
        if len(coincidencias_exactas) == 1:
            cuit_result = coincidencias_exactas[0][0]
            logging.debug(f"Regla2: coincidencia exacta única sugiere {cuit_result}")
            return cuit_result

        if provincia:
            cod_prov = PROVINCIAS_CON_NUMERO.get(provincia.strip().upper())
            if cod_prov is None:
                logging.info(f"Regla2: provincia '{provincia}' no reconocida, retornando multiples")
                return resultados

            try:
                resultados = ejecutar_busqueda_prov(conn_read, razon_social=razon_social_limpia, cod_provincia=cod_prov) or []
            except Exception as e:
                logging.error(f"Regla2: error en busqueda_prov razon_social='{razon_social}' cod_prov={cod_prov}: {e}")
                return None

            if not resultados:
                logging.debug(f"Regla2: sin resultados para provincia cod_prov={cod_prov}")
                return None

            try:
                resultados = [(r[3], r[4]) for r in resultados]
            except Exception:
                logging.error(f"Regla2: estructura inesperada en resultados por provincia para razon_social='{razon_social}'")
                return None

            if len(resultados) == 1:
                cuit_result = resultados[0][0]
                logging.debug(f"Regla2: resultado único por provincia sugiere {cuit_result}")
                return cuit_result
            else:
                logging.info(f"Regla2: multiples resultados tras filtrar por provincia ({len(resultados)})")
                return resultados
        else:
            logging.info(f"Regla2: multiples resultados sin provincia para razon_social='{razon_social}' ({len(resultados)})")
            return resultados
    else:
        logging.debug(f"Regla2: sin razon_social para buscar")
        return None
    
def _normalizar_razon_social(texto):
    """Normalización para modo 'masivos': compacta siglas pero no las elimina, limita a 15 palabras.
    Distinto de helpers.buscar_cuit.limpiar_razon_social, que usa modo 'buscador'."""
    if not texto:
        return ""

    texto = re.sub(r'\s+', ' ', texto.strip().upper())

    texto = re.sub(r'\bS[\s\.]*A[\s\.]*S\b', 'SAS', texto)
    texto = re.sub(r'\bS[\s\.]*R[\s\.]*L\b', 'SRL', texto)
    texto = re.sub(r'\bS[\s\.]*A\b', 'SA', texto)

    palabras = texto.split()
    palabras = [p for p in palabras if len(p) > 1 and any(c.isalnum() for c in p)]
    palabras = palabras[:15]

    return " ".join(palabras)


#Purgo el nombre de , . y tildes
def purgar_nombre(nombre):
    nfkd = unicodedata.normalize('NFKD', nombre)
    sin_tildes = "".join([c for c in nfkd if not unicodedata.combining(c)])
    limpio = sin_tildes.replace(',', '').replace('.', '')
    return limpio.strip().upper()

def purgar_espacios(nombre):
    sin_espacios = nombre.replace(' ', '')
    return sin_espacios

def purgar_cuit(cuit):
    sin_guiones = cuit.replace('-', '')
    return sin_guiones

def seleccionar_mejor_cuit(razon_social, candidatos, umbral_diferencia=0.15):
    """
    De una lista de candidatos (cuit, nombre), selecciona el mejor match
    comparando con razon_social usando palabras ordenadas + SequenceMatcher.
    Retorna el CUIT si hay un ganador claro, None si hay ambigüedad.
    """
    def normalizar(texto):
        palabras = sorted(texto.lower().strip().split())
        return ' '.join(palabras)

    base = normalizar(razon_social)
    scores = sorted(
        [(difflib.SequenceMatcher(None, base, normalizar(nombre)).ratio(), cuit, nombre)
         for cuit, nombre in candidatos],
        reverse=True
    )

    if not scores:
        return None
    if len(scores) == 1:
        return scores[0][1]

    mejor_score, mejor_cuit, mejor_nombre = scores[0]
    segundo_score = scores[1][0]

    if mejor_score - segundo_score >= umbral_diferencia:
        logging.debug(f"  [MEJOR MATCH] {mejor_nombre} (score: {mejor_score:.2f}, dif: {mejor_score - segundo_score:.2f})")
        return mejor_cuit

    logging.info(f"  [AMBIGUO] Mejor: {mejor_nombre} ({mejor_score:.2f}) vs {scores[1][2]} ({segundo_score:.2f})")
    return None


def cargar_json(path):
    with open(path, 'r', encoding='utf-8') as f:
        return json.load(f)

def consumir_result_sets(cursor):
    try:
        while cursor.nextset():
            pass
    except Exception:
        pass


