"""
Endpoints del canal de correo.

`Send` conserva el contrato de `/mail/send/template` que ya usan los sistemas
internos. Lo que cambia es lo de debajo: antes construia un `MailsUtil` que
pedia un token a `https://api-mails.zurco.com.mx` y le reenviaba el mensaje
por HTTP, con la clave escrita en el codigo. Ahora se compone y se envia aqui.

`SendMail` es el endpoint general nuevo, con control sobre remitente,
destinatarios, adjuntos y plantillas del proveedor.

`MailStatus` permite consultar que paso con un envio, algo que antes no era
posible: la respuesta solo decia `send_status: true|false` y ahi se acababa.
"""

from __future__ import annotations

import logging

from django.conf import settings
from drf_spectacular.utils import extend_schema
from rest_framework import status
from rest_framework.generics import RetrieveAPIView
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from core.authentications.token_auth import TokenAuthentication
from core.permissions import HasScope
from keys.models.api_keys import SCOPE_MAIL_SEND, SCOPE_MESSAGES_READ
from notifications.channels.email.base import ProviderConfigurationError
from notifications.channels.email.registry import UnknownProvider, get_provider
from notifications.models.message import Channel, MessageStatus, NotificationMessage
from notifications.querysets import mensajes_visibles_para
from notifications.serializers.mail import MailSendSerializer, MailTemplateSerializer
from notifications.serializers.message import NotificationMessageSerializer
from notifications.services.mailer import dispatch, register_message
from notifications.services.templates import render_notification_email
from notifications.throttling import SendThrottle

logger = logging.getLogger("notifications.mail")

ESTADOS_CORRECTOS = {MessageStatus.SENT, MessageStatus.DELIVERED}


class Send(APIView):
    """
    Envia el correo de notificacion de sistema.

    Contrato heredado, sin cambios para el cliente:

        {"to": [...], "to_cco": [...], "system": "...", "notification": "..."}
    """

    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated, HasScope]
    required_scope = SCOPE_MAIL_SEND
    throttle_classes = [SendThrottle]

    @extend_schema(
        summary="Enviar notificación de sistema por correo",
        request=MailTemplateSerializer,
        responses={200: None, 202: None, 400: None, 502: None, 503: None},
    )
    def post(self, request, format=None):
        serializer = MailTemplateSerializer(
            data=request.data, context={"request": request}
        )
        serializer.is_valid(raise_exception=True)
        datos = serializer.validated_data

        if not settings.EMAIL_DEFAULT_FROM:
            logger.error(
                "EMAIL_DEFAULT_FROM no esta configurado; no se puede componer el envio"
            )
            return _error(
                request,
                "sender_not_configured",
                "El remitente por defecto no esta configurado.",
                status.HTTP_503_SERVICE_UNAVAILABLE,
            )

        asunto, html = render_notification_email(
            datos["system"], datos["notification"]
        )

        payload = {
            "from_email": settings.EMAIL_DEFAULT_FROM,
            "from_name": settings.EMAIL_DEFAULT_FROM_NAME,
            "reply_to": settings.EMAIL_DEFAULT_REPLY_TO,
            "to": datos["to"],
            "cc": datos["cc"],
            "bcc": datos["bcc"],
            "subject": asunto,
            "html": html,
            "categories": ["notificacion-sistema"],
        }

        return _enviar(request, payload, send_async=True)


class SendMail(APIView):
    """Envio de correo con control completo del mensaje."""

    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated, HasScope]
    required_scope = SCOPE_MAIL_SEND
    throttle_classes = [SendThrottle]
    parser_classes = [JSONParser, MultiPartParser, FormParser]

    @extend_schema(
        summary="Enviar un correo",
        description=(
            "Acepta `application/json` (adjuntos en base64) o "
            "`multipart/form-data` (adjuntos como archivos).\n\n"
            "Con `send_async=true` (por defecto) responde `202` en cuanto "
            "encola el envio; con `send_async=false` espera al proveedor."
        ),
        request=MailSendSerializer,
        responses={200: None, 202: None, 400: None, 502: None, 503: None},
    )
    def post(self, request, format=None):
        serializer = MailSendSerializer(
            data=request.data, context={"request": request}
        )
        serializer.is_valid(raise_exception=True)
        datos = serializer.validated_data

        return _enviar(
            request,
            datos,
            send_async=datos.get("send_async", True),
            provider_name=datos.get("provider") or "",
        )


class MailStatus(RetrieveAPIView):
    """Estado de un envio y sus eventos de entrega."""

    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated, HasScope]
    required_scope = SCOPE_MESSAGES_READ
    serializer_class = NotificationMessageSerializer
    lookup_field = "pk"

    def get_queryset(self):
        # Solo los envios de la propia cuenta. Ver `mensajes_visibles_para`.
        return mensajes_visibles_para(
            NotificationMessage.objects.filter(
                channel=Channel.EMAIL
            ).prefetch_related("events"),
            getattr(self.request, "api_key_record", None),
        )

    @extend_schema(
        summary="Consultar el estado de un envio",
        responses={200: NotificationMessageSerializer, 404: None},
    )
    def get(self, request, *args, **kwargs):
        return super().get(request, *args, **kwargs)


# ---------------------------------------------------------------------------
# Ayudas compartidas
# ---------------------------------------------------------------------------


def _enviar(request, datos: dict, *, send_async: bool, provider_name: str = ""):
    """Resuelve el proveedor, registra el envio y lo despacha."""
    # Se resuelve antes de registrar nada: si el proveedor no esta
    # configurado, es mejor rechazar que aceptar un envio que ya se sabe que
    # va a fallar.
    try:
        provider = get_provider(provider_name)
    except UnknownProvider as exc:
        return _error(
            request, "provider_unavailable", str(exc), status.HTTP_400_BAD_REQUEST
        )
    except ProviderConfigurationError as exc:
        logger.error(
            "Proveedor de correo mal configurado: %s",
            exc,
            extra={"provider": provider_name or "por defecto"},
        )
        return _error(
            request,
            "provider_not_configured",
            "El proveedor de correo no esta disponible en este momento.",
            status.HTTP_503_SERVICE_UNAVAILABLE,
        )

    message, _payload = register_message(
        datos,
        provider_name=provider.name,
        request=request,
        api_key=getattr(request, "api_key_record", None),
    )

    dispatch(
        message,
        send_async=send_async,
        request_id=getattr(request, "request_id", ""),
    )

    if send_async:
        return Response(
            _cuerpo_creado(request, message, queued=True),
            status=status.HTTP_202_ACCEPTED,
        )

    message.refresh_from_db()
    if message.status in ESTADOS_CORRECTOS:
        return Response(
            _cuerpo_creado(request, message, queued=False),
            status=status.HTTP_200_OK,
        )

    logger.warning(
        "Envio sincrono fallido",
        extra={
            "message_id": str(message.id),
            "provider": message.provider,
            "error_code": message.error_code,
        },
    )
    return Response(
        {
            "status": "error",
            "error": {
                "code": message.error_code or "send_failed",
                "message": "El proveedor no pudo enviar el mensaje.",
                "details": None,
            },
            "message_id": str(message.id),
            "request_id": getattr(request, "request_id", ""),
        },
        status=status.HTTP_502_BAD_GATEWAY,
    )


def _cuerpo_creado(request, message: NotificationMessage, *, queued: bool) -> dict:
    return {
        "status": message.status,
        "message_id": str(message.id),
        "channel": message.channel,
        "provider": message.provider,
        "queued": queued,
        "recipients": message.recipient_count,
        "request_id": getattr(request, "request_id", ""),
    }


def _error(request, code: str, message: str, status_code: int) -> Response:
    return Response(
        {
            "status": "error",
            "error": {"code": code, "message": message, "details": None},
            "request_id": getattr(request, "request_id", ""),
        },
        status=status_code,
    )
