"""
Serializacion del contenido pendiente de enviar, para poder guardarlo entre
la peticion HTTP y el worker que lo procesa.

Se usa JSON comprimido con gzip en lugar de `pickle`: deserializar `pickle`
ejecuta codigo, asi que basta con que alguien consiga escribir en esa columna
para lograr ejecucion remota. JSON no tiene esa propiedad.

El campo `kind` distingue el contenido de correo del de mensajeria. Los blobs
escritos antes de existir ese campo se interpretan como correo, que es lo que
habia entonces: sin eso, un despliegue con la cola a medias perderia los
envios en vuelo.
"""

from __future__ import annotations

import base64
import gzip
import json

from notifications.channels.email.base import Attachment, EmailPayload
from notifications.channels.messaging.base import MessagingPayload

SCHEMA_VERSION = 1

KIND_EMAIL = "email"
KIND_MESSAGING = "messaging"


def payload_to_bytes(payload: EmailPayload) -> bytes:
    """Serializa un `EmailPayload` a JSON comprimido."""
    document = {
        "v": SCHEMA_VERSION,
        "kind": KIND_EMAIL,
        "from_email": payload.from_email,
        "from_name": payload.from_name,
        "reply_to": payload.reply_to,
        "to": list(payload.to),
        "cc": list(payload.cc),
        "bcc": list(payload.bcc),
        "subject": payload.subject,
        "html": payload.html,
        "text": payload.text,
        "template_id": payload.template_id,
        "template_data": dict(payload.template_data),
        "categories": list(payload.categories),
        "headers": dict(payload.headers),
        "custom_args": dict(payload.custom_args),
        "attachments": [
            {
                "filename": a.filename,
                "content": base64.b64encode(a.content).decode("ascii"),
                "content_type": a.content_type,
                "content_id": a.content_id,
                "inline": a.inline,
            }
            for a in payload.attachments
        ],
    }
    return gzip.compress(json.dumps(document).encode("utf-8"), compresslevel=6)


def payload_from_bytes(raw: bytes) -> EmailPayload | MessagingPayload:
    """
    Reconstruye el contenido guardado, sea de correo o de mensajeria.

    Quien llama no tiene que saber de que canal es: el propio documento lo
    dice.
    """
    document = _decode(raw)

    if document.get("kind", KIND_EMAIL) == KIND_MESSAGING:
        return _messaging_from_document(document)
    return _email_from_document(document)


def _decode(raw: bytes) -> dict:
    document = json.loads(gzip.decompress(bytes(raw)).decode("utf-8"))

    version = document.get("v")
    if version != SCHEMA_VERSION:
        raise ValueError(
            f"Version de payload no soportada: {version} (se esperaba {SCHEMA_VERSION})."
        )
    return document


def messaging_payload_to_bytes(payload: MessagingPayload) -> bytes:
    """Serializa un `MessagingPayload` a JSON comprimido."""
    document = {
        "v": SCHEMA_VERSION,
        "kind": KIND_MESSAGING,
        "to": payload.to,
        "channel": payload.channel,
        "body": payload.body,
        "template_sid": payload.template_sid,
        "template_variables": dict(payload.template_variables),
        "status_callback": payload.status_callback,
        "custom_args": dict(payload.custom_args),
    }
    return gzip.compress(json.dumps(document).encode("utf-8"), compresslevel=6)


def _messaging_from_document(document: dict) -> MessagingPayload:
    return MessagingPayload(
        to=document["to"],
        channel=document.get("channel", "sms"),
        body=document.get("body", ""),
        template_sid=document.get("template_sid", ""),
        template_variables=document.get("template_variables", {}),
        status_callback=document.get("status_callback", ""),
        custom_args=document.get("custom_args", {}),
    )


def _email_from_document(document: dict) -> EmailPayload:
    attachments = tuple(
        Attachment(
            filename=item["filename"],
            content=base64.b64decode(item["content"]),
            content_type=item.get("content_type", "application/octet-stream"),
            content_id=item.get("content_id", ""),
            inline=bool(item.get("inline")),
        )
        for item in document.get("attachments", [])
    )

    return EmailPayload(
        from_email=document["from_email"],
        from_name=document.get("from_name", ""),
        reply_to=document.get("reply_to", ""),
        to=tuple(document.get("to", ())),
        cc=tuple(document.get("cc", ())),
        bcc=tuple(document.get("bcc", ())),
        subject=document.get("subject", ""),
        html=document.get("html", ""),
        text=document.get("text", ""),
        template_id=document.get("template_id", ""),
        template_data=document.get("template_data", {}),
        categories=tuple(document.get("categories", ())),
        headers=document.get("headers", {}),
        custom_args=document.get("custom_args", {}),
        attachments=attachments,
    )
