"""Pruebas de los canales de SMS y WhatsApp."""

from __future__ import annotations

import pytest
from django.urls import reverse

from notifications.models.message import Channel, NotificationMessage
from notifications.models.numbers import Numbers
from notifications.serializers.phone import normalize_phone

pytestmark = pytest.mark.django_db


def detalles(respuesta) -> dict:
    cuerpo = respuesta.json()
    return cuerpo.get("error", {}).get("details") or cuerpo


# ---------------------------------------------------------------------------
# Normalizacion de numeros
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("entrada", "esperado"),
    [
        ("+525512345678", "+525512345678"),
        ("+52 55 1234 5678", "+525512345678"),
        ("+52 (55) 1234-5678", "+525512345678"),
        ("+1.202.555.0173", "+12025550173"),
    ],
)
def test_se_normalizan_los_separadores(entrada, esperado):
    """Escribir el numero con espacios o guiones es normal y no lo cambia."""
    assert normalize_phone(entrada) == esperado


@pytest.mark.parametrize(
    "invalido",
    [
        "",
        "5512345678",          # sin codigo de pais
        "no-es-un-numero",
        "+0123456789",         # el codigo de pais no puede empezar por 0
        "+52",                 # demasiado corto
        "+5255123456789012345",  # demasiado largo
        "correo@ejemplo.com",
    ],
)
def test_se_rechazan_los_numeros_invalidos(invalido):
    """
    El serializer anterior era `CharField()`, asi que cualquier cadena llegaba
    a Twilio y el fallo se convertia en un 'Invalid data' sin detalle.
    """
    from rest_framework import serializers

    with pytest.raises(serializers.ValidationError):
        normalize_phone(invalido)


def test_sin_codigo_de_pais_configurado_se_exige_explicito(settings):
    from rest_framework import serializers

    settings.DEFAULT_PHONE_COUNTRY_CODE = ""
    with pytest.raises(serializers.ValidationError):
        normalize_phone("5512345678")


def test_con_codigo_de_pais_configurado_se_completa(settings):
    settings.DEFAULT_PHONE_COUNTRY_CODE = "+52"
    assert normalize_phone("5512345678") == "+525512345678"


# ---------------------------------------------------------------------------
# SMS
# ---------------------------------------------------------------------------


def test_se_envia_un_sms(api_autenticada):
    respuesta = api_autenticada.post(
        reverse("v1:sms-send"),
        {"to": ["+525512345678"], "body": "Tu folio ABC-123 fue creado."},
        format="json",
    )

    assert respuesta.status_code == 202
    cuerpo = respuesta.json()
    assert cuerpo["channel"] == Channel.SMS
    assert cuerpo["requested"] == 1

    mensaje = NotificationMessage.objects.get(pk=cuerpo["messages"][0]["message_id"])
    assert mensaje.channel == Channel.SMS
    assert mensaje.to == ["+525512345678"]


def test_cada_destinatario_es_un_envio_independiente(api_autenticada):
    """
    Cada SMS se factura por separado y puede fallar por su cuenta, asi que
    tiene su propia fila de auditoria y su propio identificador.
    """
    respuesta = api_autenticada.post(
        reverse("v1:sms-send"),
        {"to": ["+525512345678", "+525587654321"], "body": "Hola"},
        format="json",
    )

    assert respuesta.status_code == 202
    assert respuesta.json()["requested"] == 2
    assert NotificationMessage.objects.filter(channel=Channel.SMS).count() == 2

    identificadores = {m["message_id"] for m in respuesta.json()["messages"]}
    assert len(identificadores) == 2


def test_se_eliminan_los_numeros_repetidos(api_autenticada):
    """Mandar dos veces el mismo SMS al mismo numero se cobra dos veces."""
    respuesta = api_autenticada.post(
        reverse("v1:sms-send"),
        {"to": ["+525512345678", "+52 55 1234 5678"], "body": "Hola"},
        format="json",
    )

    assert respuesta.json()["requested"] == 1


def test_se_limita_el_numero_de_destinatarios(api_autenticada, settings):
    settings.MESSAGING_MAX_RECIPIENTS = 3
    respuesta = api_autenticada.post(
        reverse("v1:sms-send"),
        {"to": [f"+5255123456{i:02d}" for i in range(10)], "body": "Hola"},
        format="json",
    )
    assert respuesta.status_code == 400


def test_se_limita_la_longitud_del_cuerpo(api_autenticada, settings):
    """Cada 153 caracteres son un SMS mas que se factura."""
    settings.MESSAGING_MAX_BODY_CHARS = 100
    respuesta = api_autenticada.post(
        reverse("v1:sms-send"),
        {"to": ["+525512345678"], "body": "x" * 500},
        format="json",
    )
    assert respuesta.status_code == 400


def test_se_rechaza_un_cuerpo_vacio(api_autenticada):
    respuesta = api_autenticada.post(
        reverse("v1:sms-send"),
        {"to": ["+525512345678"], "body": "   "},
        format="json",
    )
    assert respuesta.status_code == 400


# ---------------------------------------------------------------------------
# WhatsApp
# ---------------------------------------------------------------------------


def test_se_envia_una_notificacion_de_sistema(api_autenticada):
    respuesta = api_autenticada.post(
        reverse("v1:whatsapp-send-template"),
        {
            "to": ["+525512345678"],
            "system": "Ventanilla Notarial",
            "notification": "El folio HUIX-970630 se creó correctamente.",
        },
        format="json",
    )

    assert respuesta.status_code == 202
    mensaje = NotificationMessage.objects.filter(channel=Channel.WHATSAPP).first()
    assert mensaje is not None


def test_se_pregunta_la_continuidad_una_vez_al_dia(api_autenticada, settings):
    """
    Se conserva el comportamiento anterior: se pregunta al destinatario si
    quiere seguir recibiendo notificaciones, pero no en cada envio.
    """
    settings.WHATSAPP_REPLY_PROMPT_DAYS = 1
    cuerpo = {
        "to": ["+525512345678"],
        "system": "S",
        "notification": "N",
    }

    # Primer envio: el numero es nuevo, no se pregunta.
    api_autenticada.post(reverse("v1:whatsapp-send-template"), cuerpo, format="json")
    assert NotificationMessage.objects.filter(channel=Channel.WHATSAPP).count() == 1

    # Se simula que la ultima pregunta fue hace dos dias.
    from django.utils import timezone

    registro = Numbers.objects.get(number="+525512345678")
    registro.replay_date = timezone.now() - timezone.timedelta(days=2)
    registro.save(update_fields=["replay_date"])

    from notifications.tests.conftest import _reautenticar

    _reautenticar(api_autenticada)
    api_autenticada.post(reverse("v1:whatsapp-send-template"), cuerpo, format="json")

    # La notificacion mas la pregunta de continuidad.
    assert NotificationMessage.objects.filter(channel=Channel.WHATSAPP).count() == 3


def test_la_fecha_de_pregunta_no_se_pisa_sola(api_autenticada):
    """
    `replay_date` era `auto_now=True`, con lo que se actualizaba en cada
    guardado del registro y dejaba de medir lo que decia medir.
    """
    from django.utils import timezone

    registro = Numbers.objects.create(number="+525599999999")
    marca = timezone.now() - timezone.timedelta(days=5)
    registro.replay_date = marca
    registro.save(update_fields=["replay_date"])

    registro.refresh_from_db()
    assert abs((registro.replay_date - marca).total_seconds()) < 1


def test_se_valida_el_identificador_de_plantilla(api_autenticada):
    respuesta = api_autenticada.post(
        reverse("v1:whatsapp-send"),
        {"to": ["+525512345678"], "vars": {"1": "Ana"}, "template": "no-es-valido"},
        format="json",
    )
    assert respuesta.status_code == 400


def test_se_envia_una_plantilla_valida(api_autenticada):
    respuesta = api_autenticada.post(
        reverse("v1:whatsapp-send"),
        {
            "to": ["+525512345678"],
            "vars": {"1": "Ana", "2": "ABC-123"},
            "template": "HX" + "a" * 32,
        },
        format="json",
    )

    assert respuesta.status_code == 202
    mensaje = NotificationMessage.objects.filter(channel=Channel.WHATSAPP).first()
    assert mensaje.template_id == "HX" + "a" * 32


def test_las_variables_deben_ser_un_objeto(api_autenticada):
    respuesta = api_autenticada.post(
        reverse("v1:whatsapp-send"),
        {"to": ["+525512345678"], "vars": ["Ana"], "template": "HX" + "a" * 32},
        format="json",
    )
    assert respuesta.status_code == 400


# ---------------------------------------------------------------------------
# Resultado por destinatario
# ---------------------------------------------------------------------------


def test_un_fallo_parcial_se_reporta_por_destinatario(api_autenticada, monkeypatch):
    """
    Antes, si fallaba uno de los destinatarios se devolvia `400 Invalid data`
    sin decir cuales se habian enviado ya. El cliente no podia reintentar sin
    arriesgarse a duplicar.
    """
    from notifications.channels.common import SendResult
    from notifications.channels.messaging.console import ConsoleMessagingProvider

    original = ConsoleMessagingProvider.send

    def falla_el_segundo(self, payload):
        if payload.to.endswith("2"):
            return SendResult.failure(
                self.name,
                error_code="twilio_21211",
                error_message="numero invalido",
                status_code=400,
                retryable=False,
            )
        return original(self, payload)

    monkeypatch.setattr(ConsoleMessagingProvider, "send", falla_el_segundo)

    respuesta = api_autenticada.post(
        reverse("v1:sms-send"),
        {
            "to": ["+525512345671", "+525512345672"],
            "body": "Hola",
            "send_async": False,
        },
        format="json",
    )

    assert respuesta.status_code == 207
    cuerpo = respuesta.json()
    assert cuerpo["status"] == "partial"
    assert cuerpo["sent"] == 1
    assert cuerpo["failed"] == 1

    fallido = next(m for m in cuerpo["messages"] if m["status"] == "failed")
    assert fallido["to"] == "+525512345672"
    assert fallido["error_code"] == "twilio_21211"
