"""Genera candidatos de email a partir de nombre, apellidos y dominio.

Funcion pura de OSINT pasivo: produce los patrones de direccion de correo
mas habituales en organizaciones, sin tocar la red. Util como punto de
partida para verificacion posterior (MX, catch-all, validacion SMTP, etc.).
"""

import unicodedata


def _ascii_lower(text: str) -> str:
    """Normaliza un texto a ASCII en minusculas.

    Translitera acentos y caracteres latinos (a, e, n, ...) a su forma
    ASCII, pasa a minusculas y elimina cualquier caracter que no sea
    alfanumerico. No introduce separadores (a diferencia de un slug).

    Args:
        text: texto de entrada (puede contener acentos, ñ, mayusculas).

    Returns:
        cadena ASCII en minusculas formada solo por [a-z0-9].
    """
    # NFKD descompone los caracteres acentuados en base + diacritico.
    decomposed = unicodedata.normalize("NFKD", text)
    stripped = "".join(c for c in decomposed if not unicodedata.combining(c))
    lowered = stripped.lower()
    return "".join(c for c in lowered if c.isalnum())


def guess_email_formats(nombre: str, apellidos: str, dominio: str) -> list:
    """Genera candidatos de email comunes a partir de identidad y dominio.

    Combina el nombre y los apellidos en los patrones de direccion mas
    frecuentes (nombre.apellido, inicial+apellido, apellido.nombre, etc.),
    normalizando acentos/ñ a ASCII y minusculas. Si hay dos apellidos
    tambien genera variantes que los unen. Deduplica preservando el orden
    de aparicion.

    Args:
        nombre: nombre de pila (puede incluir varios tokens separados por
            espacio; se usa el primero como nombre principal).
        apellidos: uno o dos apellidos separados por espacio.
        dominio: dominio de correo sin arroba (ej. "empresa.com").

    Returns:
        lista de strings "<local>@<dominio>" con los candidatos, en el
        orden en que se generan los patrones y sin duplicados.
    """
    n = _ascii_lower(nombre.split()[0]) if nombre.split() else ""
    apellido_tokens = [_ascii_lower(a) for a in apellidos.split() if _ascii_lower(a)]
    a1 = apellido_tokens[0] if apellido_tokens else ""
    a2 = apellido_tokens[1] if len(apellido_tokens) > 1 else ""
    dom = dominio.strip().lstrip("@").lower()

    ni = n[0] if n else ""
    a1i = a1[0] if a1 else ""
    apellido_full = "".join(apellido_tokens)  # apellido1apellido2 unidos

    locals_raw = [
        n,                                    # nombre
        f"{n}.{a1}" if n and a1 else "",      # nombre.apellido
        f"{n}{a1}" if n and a1 else "",       # nombreapellido
        f"{ni}.{a1}" if ni and a1 else "",    # n.apellido
        f"{ni}{a1}" if ni and a1 else "",     # napellido
        f"{n}_{a1}" if n and a1 else "",      # nombre_apellido
        f"{a1}.{n}" if a1 and n else "",      # apellido.nombre
        f"{n}{a1i}" if n and a1i else "",     # nombre+inicial_apellido
        f"{n}.{apellido_full}" if n and a2 else "",   # nombre.apellido1apellido2
        f"{n}{apellido_full}" if n and a2 else "",    # nombreapellido1apellido2
        f"{n}.{a2}" if n and a2 else "",      # nombre.apellido2
        f"{ni}{apellido_full}" if ni and a2 else "",  # n+apellidos unidos
    ]

    seen = set()
    out = []
    for local in locals_raw:
        if not local:
            continue
        email = f"{local}@{dom}"
        if email not in seen:
            seen.add(email)
            out.append(email)
    return out
