"""
Serializers del canal de WhatsApp.

Se conservan los dos contratos que ya usaban los sistemas internos:

* `WhatsAppTemplateSerializer` -> `/whatsapp/send/template`, que compone el
  texto de la notificacion de sistema.
* `WhatsAppConversationSerializer` -> `/whatsapp/send`, que envia una
  plantilla aprobada de Twilio con sus variables.

Los nombres de los endpoints estan cruzados respecto a lo que hacen (el que se
llama "template" manda texto libre y el que se llama "send" manda la
plantilla), pero se dejan como estan: renombrarlos rompería a los clientes sin
ganar nada funcional.
"""

from __future__ import annotations

from rest_framework import serializers

from notifications.serializers.phone import PhoneListField


class WhatsAppTemplateSerializer(serializers.Serializer):
    """Notificacion de sistema por WhatsApp, con el texto compuesto aqui."""

    to = PhoneListField(allow_empty=False)
    system = serializers.CharField(max_length=200)
    notification = serializers.CharField(max_length=2000)
    send_async = serializers.BooleanField(default=True)

    def validate_system(self, value: str) -> str:
        return _sin_saltos(value, "system")

    def validate_notification(self, value: str) -> str:
        return _sin_saltos(value, "notification", permitir_saltos=True)


class WhatsAppConversationSerializer(serializers.Serializer):
    """Envio de una plantilla aprobada de Twilio."""

    to = PhoneListField(allow_empty=False)
    vars = serializers.JSONField()
    template = serializers.CharField(max_length=64)
    send_async = serializers.BooleanField(default=True)

    def validate_template(self, value: str) -> str:
        value = value.strip()
        # Los identificadores de contenido de Twilio son HX + 32 hexadecimales.
        if not value.startswith("HX") or len(value) != 34:
            raise serializers.ValidationError(
                "El identificador de plantilla debe tener el formato HX seguido "
                "de 32 caracteres hexadecimales."
            )
        return value

    def validate_vars(self, value):
        if not isinstance(value, dict):
            raise serializers.ValidationError(
                "Las variables de la plantilla deben enviarse como un objeto."
            )

        # Twilio espera las variables indexadas por posicion en forma de
        # cadena: {"1": "Ana", "2": "1234"}.
        limpio = {}
        for clave, valor in value.items():
            if not isinstance(valor, (str, int, float)):
                raise serializers.ValidationError(
                    f"El valor de la variable '{clave}' debe ser texto o numero."
                )
            texto = str(valor)
            if len(texto) > 1000:
                raise serializers.ValidationError(
                    f"El valor de la variable '{clave}' es demasiado largo."
                )
            limpio[str(clave)] = texto

        if len(limpio) > 50:
            raise serializers.ValidationError(
                "Una plantilla no admite mas de 50 variables."
            )
        return limpio


def _sin_saltos(value: str, campo: str, *, permitir_saltos: bool = False) -> str:
    """
    Rechaza los caracteres de control.

    En WhatsApp no hay cabeceras que inyectar, pero un `\\x00` o un `\\r` en
    medio del texto rompe la representacion en algunos clientes y ensucia los
    registros.
    """
    prohibidos = set(range(0x00, 0x20)) | {0x7F}
    if permitir_saltos:
        prohibidos -= {0x0A}  # se admite el salto de linea

    if any(ord(caracter) in prohibidos for caracter in value):
        raise serializers.ValidationError(
            f"El campo '{campo}' contiene caracteres de control no admitidos."
        )
    return value
