"""Serializa un contacto (dict) a un VCARD 3.0 con soporte multi-valor.

Generaliza el ``_build_vcard`` inline de ``osint_web/server/main.py`` (que solo
emitia un TEL y un EMAIL): aqui acepta listas de telefonos, emails y direcciones
y emite una linea por elemento. Es una funcion pura — solo compone texto, sin red
ni disco. La unica excepcion posible es ``ValueError`` por validacion de entrada
(falta de identificador), lo cual es aceptable para una funcion pura.
"""

# Orden + nombre de propiedad X-OSINT para cada clave del bloque osint.
_OSINT_FIELDS = (
    ("dni", "X-OSINT-DNI"),
    ("pais", "X-OSINT-PAIS"),
    ("contexto", "X-OSINT-CONTEXTO"),
    ("sexo", "X-OSINT-SEXO"),
    ("fecha_nacimiento", "X-OSINT-FECHA-NACIMIENTO"),
)


def _vcard_escape(value: str) -> str:
    """Escapa un valor de texto para una linea vCard (RFC 6350).

    Reglas: ``\\`` -> ``\\\\``, salto de linea -> ``\\n``, ``,`` -> ``\\,``,
    ``;`` -> ``\\;``. Se aplica al contenido de cada propiedad, NO a los
    separadores estructurales del ADR.

    El retorno de carro ``\\r`` crudo se ELIMINA (no se escapa): un ``\\r`` solo,
    sin ``\\n`` que lo siga, sobrevive al escape de ``\\n`` y queda como carácter de
    control dentro del valor. Varios parsers de vCard (y el propio ``_unfold_lines``
    de osint_web, que normaliza ``\\r`` a ``\\n``) lo tratan como un separador de
    línea, lo que permitiría inyectar propiedades nuevas (p. ej. ``X-OSINT-DNI``)
    en la tarjeta. Eliminarlo cierra ese vector, en paridad con el escape iCal.
    """
    return (
        value.replace("\\", "\\\\")
        .replace("\r", "")
        .replace("\n", "\\n")
        .replace(",", "\\,")
        .replace(";", "\\;")
    )


def _as_list(value) -> list:
    """Normaliza un valor a lista: ``None`` -> ``[]``, string suelto -> ``[s]``.

    Tolera que una clave que deberia ser lista venga como string suelto.
    """
    if value is None:
        return []
    if isinstance(value, str):
        return [value]
    if isinstance(value, (list, tuple)):
        return list(value)
    return [value]


def _pick(contact: dict, *keys):
    """Devuelve el primer valor no vacio entre ``keys`` (acepta ES/EN)."""
    for key in keys:
        val = contact.get(key)
        if val:
            return val
    return None


def build_vcard(contact: dict) -> str:
    """Serializa un contacto (dict) a un VCARD 3.0 con soporte multi-valor.

    Args:
        contact: dict con claves opcionales (acepta nombre ES o EN):
            - ``uid`` / ``slug``: identificador del vCard. Uno obligatorio.
            - ``fn`` / ``nombre``: nombre completo (FN).
            - ``aliases``: lista -> NICKNAME (CSV escapado).
            - ``org``: organizacion -> ORG.
            - ``tels`` / ``telefonos``: lista -> una linea TEL;TYPE=CELL por item.
            - ``emails`` / ``correos``: lista -> una linea EMAIL;TYPE=INTERNET por item.
            - ``adrs`` / ``direcciones``: lista -> una linea ADR;TYPE=HOME por item
              (la direccion va en el componente street del ADR estructurado).
            - ``osint``: dict con ``dni, pais, contexto, sexo, fecha_nacimiento``
              -> lineas X-OSINT-* (solo las presentes/no vacias).
            - ``note`` / ``notas``: texto -> NOTE.

    Returns:
        Texto VCARD 3.0 con lineas separadas por CRLF, terminando en
        ``END:VCARD\\r\\n``.

    Raises:
        ValueError: si faltan ``uid`` y ``slug`` (no hay identificador).
    """
    uid = contact.get("uid") or contact.get("slug")
    if not uid:
        raise ValueError("build_vcard: falta identificador (uid o slug)")
    uid = str(uid).strip()

    nombre = _pick(contact, "fn", "nombre")
    nombre = str(nombre).strip() if nombre else uid

    lines = [
        "BEGIN:VCARD",
        "VERSION:3.0",
        "UID:%s" % _vcard_escape(uid),
        "FN:%s" % _vcard_escape(nombre),
    ]

    aliases = _as_list(contact.get("aliases"))
    if aliases:
        joined = ",".join(_vcard_escape(str(a)) for a in aliases)
        lines.append("NICKNAME:%s" % joined)

    org = contact.get("org")
    if org:
        lines.append("ORG:%s" % _vcard_escape(str(org)))

    for tel in _as_list(_pick(contact, "tels", "telefonos")):
        lines.append("TEL;TYPE=CELL:%s" % _vcard_escape(str(tel)))

    for email in _as_list(_pick(contact, "emails", "correos")):
        lines.append("EMAIL;TYPE=INTERNET:%s" % _vcard_escape(str(email)))

    for adr in _as_list(_pick(contact, "adrs", "direcciones")):
        # ADR estructurado: 7 componentes separados por ';' SIN escapar los
        # separadores. La direccion va en el 3er componente (street); el resto
        # vacios: po-box;extended;street;locality;region;postal-code;country.
        street = _vcard_escape(str(adr))
        lines.append("ADR;TYPE=HOME:;;%s;;;;" % street)

    osint = contact.get("osint")
    if isinstance(osint, dict):
        for key, x_name in _OSINT_FIELDS:
            val = osint.get(key)
            if val:
                lines.append("%s:%s" % (x_name, _vcard_escape(str(val))))

    note = _pick(contact, "note", "notas")
    if note:
        lines.append("NOTE:%s" % _vcard_escape(str(note)))

    lines.append("END:VCARD")
    return "\r\n".join(lines) + "\r\n"
