"""Endpoints de dominios."""

from drf_spectacular.utils import extend_schema
from rest_framework import serializers, status
from rest_framework.response import Response

from apps.core.api import BaseApiView
from apps.core.errors import NotFound
from apps.domains.models import Domain
from apps.domains.services import (
    check_access,
    company_domain,
    create_company_domain,
    shared_domains,
    update_settings,
)
from apps.jobs.budget import remaining


class DomainSerializer(serializers.ModelSerializer):
    is_operational = serializers.BooleanField(read_only=True)

    class Meta:
        model = Domain
        fields = [
            'id',
            'hostname',
            'property_type',
            'property_uri',
            'access_state',
            'access_checked_at',
            'access_error',
            'access_error_code',
            'is_operational',
            'daily_inspection_budget',
            'manual_reserve',
            'notifications_enabled',
            'created_at',
            # Nulo mientras el sitio esté conectado. Viaja aunque el listado no
            # devuelva los dados de baja: el detalle por identificador sí los
            # contesta, y es este campo el que le explica a quien guardó ese
            # identificador por qué dejó de recibir datos.
            'deactivated_at',
        ]


class DomainCreateSerializer(serializers.Serializer):
    hostname = serializers.CharField()
    property_type = serializers.ChoiceField(choices=['DOMAIN', 'URL_PREFIX'])


class DomainSettingsSerializer(serializers.Serializer):
    """
    Cuerpo del `PATCH` (FR-057). Documenta el contrato; no lo valida.

    La validación entera vive en `update_settings` porque la regla que importa
    —la reserva manual no puede superar el presupuesto diario— relaciona dos
    campos y depende de lo que el dominio ya tenía guardado. Repartirla entre el
    serializador y el servicio dejaría a la interfaz de Inertia, que no pasa por
    DRF, con la mitad de las comprobaciones.
    """

    notifications_enabled = serializers.BooleanField(required=False)
    daily_inspection_budget = serializers.IntegerField(required=False, min_value=1)
    manual_reserve = serializers.IntegerField(required=False, min_value=0)


class QuotaSerializer(serializers.Serializer):
    date = serializers.CharField()
    limit_total = serializers.IntegerField()
    manual_reserve = serializers.IntegerField()
    used_automatic = serializers.IntegerField()
    used_manual = serializers.IntegerField()
    automatic_remaining = serializers.IntegerField()
    manual_remaining = serializers.IntegerField()


class DomainListView(BaseApiView):
    @extend_schema(responses=DomainSerializer(many=True), tags=['domains'])
    def get(self, request):
        # Sólo los activos: un sitio dado de baja no se revalida y `POST
        # /batches` lo rechaza, así que listarlo sería ofrecer algo sobre lo que
        # la propia API no deja operar. El detalle por identificador sí lo
        # contesta, con su `deactivated_at`.
        domains = shared_domains()
        return Response(DomainSerializer(domains, many=True).data)

    @extend_schema(
        request=DomainCreateSerializer, responses={201: DomainSerializer}, tags=['domains']
    )
    def post(self, request):
        replay = self.replay(request)
        if replay is not None:
            return replay

        domain = create_company_domain(
            request.user,
            hostname=request.data.get('hostname', ''),
            property_type=request.data.get('property_type', ''),
        )
        response = Response(DomainSerializer(domain).data, status=status.HTTP_201_CREATED)
        return self.store(request, response)


class DomainDetailView(BaseApiView):
    @extend_schema(responses=DomainSerializer, tags=['domains'])
    def get(self, request, domain_id):
        return Response(DomainSerializer(company_domain_or_error(domain_id)).data)

    @extend_schema(request=DomainSettingsSerializer, responses=DomainSerializer, tags=['domains'])
    def patch(self, request, domain_id):
        """
        Edita la configuración del dominio (FR-057).

        Devuelve el dominio completo y no sólo lo que cambió: es la misma
        representación que entrega el `GET`, así que quien integra puede
        guardarla tal cual sin tener que fusionarla con lo que ya tenía.
        """
        domain = update_settings(company_domain_or_error(domain_id), request.data)
        return Response(DomainSerializer(domain).data)


class DomainCheckAccessView(BaseApiView):
    @extend_schema(request=None, responses=DomainSerializer, tags=['domains'])
    def post(self, request, domain_id):
        """
        Vuelve a preguntar si tenemos acceso a la propiedad.

        Devuelve el dominio con su estado ya actualizado, y no un simple «ok»:
        el resultado *es* el estado, y pedirlo en una segunda llamada abriría la
        ventana en la que las dos respuestas no coinciden.
        """
        domain = check_access(company_domain_or_error(domain_id))
        return Response(DomainSerializer(domain).data)


class DomainQuotaView(BaseApiView):
    @extend_schema(responses=QuotaSerializer, tags=['domains'])
    def get(self, request, domain_id):
        return Response(QuotaSerializer(remaining(company_domain_or_error(domain_id))).data)


def company_domain_or_error(domain_id) -> Domain:
    """
    El dominio de la compañía, o el error estructurado de la API.

    Es público y vive acá porque lo usan los endpoints de sitemaps y de lotes.
    La consulta está en `services`; esto sólo elige cómo contesta la API, que no
    es un 404 de Django sino su propio cuerpo de error.
    """
    domain = company_domain(domain_id)
    if domain is None:
        raise NotFound('Ese dominio no existe en esta compañía.')
    return domain
