"""
Contrato comun de los proveedores de correo.

El codigo anterior hablaba directamente con la API de Gmail desde la vista.
Aqui se define una frontera: la vista construye un `EmailPayload` neutro y el
proveedor se encarga de traducirlo a su propio formato. Anadir un proveedor
nuevo (Amazon SES, Mailgun, SMTP) es implementar una clase mas.

El resultado del envio (`SendResult`) y la politica de reintentos son
comunes a todos los canales y viven en `notifications/channels/common.py`; se
reexportan aqui para no romper los imports existentes.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Mapping
from dataclasses import dataclass, field
from typing import Any, ClassVar

from notifications.channels.common import (
    ProviderConfigurationError,
    SendResult,
    is_retryable_status,
)

__all__ = [
    "Attachment",
    "EmailPayload",
    "EmailProvider",
    "ProviderConfigurationError",
    "SendResult",
    "is_retryable_status",
]


@dataclass(frozen=True)
class Attachment:
    """Un archivo adjunto o una imagen incrustada en el cuerpo."""

    filename: str
    content: bytes
    content_type: str = "application/octet-stream"
    #: Identificador para referenciar la imagen desde el HTML con `cid:`.
    content_id: str = ""
    #: `True` para imagenes que se muestran dentro del mensaje en lugar de
    #: aparecer como adjunto descargable.
    inline: bool = False

    @property
    def size(self) -> int:
        return len(self.content)


@dataclass(frozen=True)
class EmailPayload:
    """Un mensaje listo para enviar, independiente del proveedor."""

    from_email: str
    to: tuple[str, ...]
    subject: str = ""
    from_name: str = ""
    reply_to: str = ""
    cc: tuple[str, ...] = ()
    bcc: tuple[str, ...] = ()
    html: str = ""
    text: str = ""
    attachments: tuple[Attachment, ...] = ()
    headers: Mapping[str, str] = field(default_factory=dict)

    #: Plantilla dinamica del proveedor (solo SendGrid).
    template_id: str = ""
    template_data: Mapping[str, Any] = field(default_factory=dict)

    #: Etiquetas para agrupar envios en las estadisticas del proveedor.
    categories: tuple[str, ...] = ()

    #: Metadatos que el proveedor devuelve en los eventos del webhook. Se usa
    #: para enlazar cada evento con su fila de auditoria.
    custom_args: Mapping[str, str] = field(default_factory=dict)

    @property
    def all_recipients(self) -> tuple[str, ...]:
        return tuple(self.to) + tuple(self.cc) + tuple(self.bcc)


class EmailProvider(ABC):
    """Interfaz que debe cumplir cualquier proveedor de correo."""

    #: Identificador corto, el mismo que acepta el campo `provider` de la API.
    name: ClassVar[str] = ""

    #: `True` si el proveedor sabe expandir plantillas del lado del servidor.
    supports_templates: ClassVar[bool] = False

    @abstractmethod
    def send(self, payload: EmailPayload) -> SendResult:
        """
        Envia el mensaje.

        No debe lanzar excepciones por fallos del proveedor: esos se reportan
        en el `SendResult`. Solo puede propagar errores de programacion.
        """

    def check_configuration(self) -> None:
        """
        Verifica que el proveedor pueda operar.

        Lanza `ProviderConfigurationError` si falta algo. Se llama al
        seleccionar el proveedor, para fallar pronto y con un mensaje claro
        en lugar de a mitad del envio.
        """
        return None
