"""
Piezas comunes a todos los canales.

El resultado de un envio y la politica de reintentos son iguales para correo,
SMS y WhatsApp, asi que viven aqui y no dentro de un canal concreto. Que el
canal de SMS tuviera que importar de `channels.email` para saber que es un
`SendResult` seria una dependencia sin sentido.

Los proveedores no lanzan excepciones ante un fallo esperado del envio:
devuelven un `SendResult` con `success=False` y una marca `retryable` que
indica si tiene sentido volver a intentarlo. Distinguir entre "el numero no
existe" (no reintentar) y "el proveedor devolvio 503" (reintentar) es lo que
evita tanto perder mensajes como martillear al proveedor.
"""

from __future__ import annotations

from dataclasses import dataclass


class ProviderConfigurationError(Exception):
    """El proveedor no tiene la configuracion minima para funcionar."""


@dataclass(frozen=True)
class SendResult:
    """Resultado de un intento de envio, sea del canal que sea."""

    success: bool
    provider: str
    message_id: str = ""
    status_code: int | None = None
    error_code: str = ""
    error_message: str = ""
    #: `True` si el fallo es temporal y merece la pena reintentar.
    retryable: bool = False

    @classmethod
    def ok(
        cls, provider: str, message_id: str = "", status_code: int | None = None
    ) -> SendResult:
        return cls(
            success=True,
            provider=provider,
            message_id=message_id,
            status_code=status_code,
        )

    @classmethod
    def failure(
        cls,
        provider: str,
        error_code: str,
        error_message: str,
        *,
        status_code: int | None = None,
        retryable: bool = False,
    ) -> SendResult:
        return cls(
            success=False,
            provider=provider,
            error_code=error_code,
            error_message=error_message,
            status_code=status_code,
            retryable=retryable,
        )


def is_retryable_status(status_code: int | None) -> bool:
    """
    Decide si un codigo HTTP del proveedor justifica un reintento.

    Se reintenta ante limites de velocidad (429) y errores del lado del
    proveedor (5xx). Un 4xx distinto de 429 significa que el mensaje esta mal
    construido y reintentarlo daria exactamente el mismo error.
    """
    if status_code is None:
        return True  # fallo de red: no llegamos a saber el resultado
    if status_code == 429:
        return True
    return status_code >= 500
