"""
Endpoints del recorrido guiado.

Son tres y ninguno calcula nada: traducen entre HTTP y `apps.onboarding.services`,
que es el mismo servicio que consume la pantalla. Es la condición del principio
IV: si la decisión de cuándo un paso está cumplido viviera en la vista, la API no
la tendría y el recorrido se comportaría distinto según por dónde se lo use.
"""

from drf_spectacular.utils import extend_schema
from rest_framework import serializers
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.response import Response

from apps.core.api import BaseApiView
from apps.credentials.api import _received_material
from apps.onboarding import services


class StepSerializer(serializers.Serializer):
    """
    Un paso, con su guía y su comprobación juntas.

    `state` distingue cuatro situaciones y no dos: `PASSED`, `MISSING` —falta algo
    que se resuelve acá—, `BLOCKED` —hace falta otro paso para poder comprobarlo—
    y `UNCONFIRMED` —Google no respondió—. Colapsarlas en «cumplido / no
    cumplido» haría que un corte de red se lea como un error de quien configura.

    **No lleva el texto del paso.** El título, el objetivo, la ruta dentro de
    Google y el ejemplo salen del catálogo del cliente por el código del paso: el
    recorrido se lee en el idioma de quien lo hace, y el servidor no sabe cuál
    es. Lo mismo `reason`, que es el código de lo que falta, con sus datos en
    `reason_params`.
    """

    code = serializers.CharField()
    position = serializers.IntegerField()
    has_example = serializers.BooleanField()
    state = serializers.CharField()
    completed = serializers.BooleanField()
    completed_at = serializers.CharField(allow_null=True)
    is_current = serializers.BooleanField()
    reason = serializers.CharField(allow_blank=True)
    reason_params = serializers.DictField()
    blocked_by = serializers.CharField(allow_blank=True)
    evidence = serializers.DictField()
    inputs = serializers.ListField(child=serializers.DictField())
    form_path = serializers.CharField()


class CreatedSerializer(serializers.Serializer):
    """Lo que el recorrido ya creó, para nombrarlo al retomar y enlazarlo al cerrar."""

    project_id = serializers.CharField(allow_null=True)
    client_email = serializers.CharField(allow_null=True)
    credential_status = serializers.CharField(allow_null=True)
    properties = serializers.ListField(child=serializers.DictField())
    domain_id = serializers.CharField(allow_null=True)
    hostname = serializers.CharField(allow_null=True)
    property_uri = serializers.CharField(allow_null=True)
    access_state = serializers.CharField(allow_null=True)
    sitemap_id = serializers.CharField(allow_null=True)
    sitemap_location = serializers.CharField(allow_null=True)
    batch_id = serializers.CharField(allow_null=True)
    batch_state = serializers.CharField(allow_null=True)
    batch_path = serializers.CharField(allow_null=True)
    coverage_path = serializers.CharField(allow_null=True)
    sitemaps_path = serializers.CharField(allow_null=True)


class OnboardingStateSerializer(serializers.Serializer):
    should_offer = serializers.BooleanField()
    current_step = serializers.CharField()
    checked_step = serializers.CharField(allow_blank=True)
    completed_steps = serializers.ListField(child=serializers.CharField())
    is_finished = serializers.BooleanField()
    is_dismissed = serializers.BooleanField()
    completed_at = serializers.CharField(allow_null=True)
    dismissed_at = serializers.CharField(allow_null=True)
    started_at = serializers.CharField(allow_null=True)
    steps = StepSerializer(many=True)
    created = CreatedSerializer()
    key_file_example = serializers.DictField()
    key_file_highlights = serializers.ListField(child=serializers.CharField())


class StepVerifySerializer(serializers.Serializer):
    """
    Lo que puede acompañar a una comprobación, según el paso.

    Todos los campos son opcionales porque comprobar sin mandar nada es una
    operación legítima: es lo que hace quien ya cargó el dato por el formulario
    completo y sólo quiere que el recorrido lo constate.
    """

    project_id = serializers.CharField(required=False)
    key_file = serializers.CharField(
        required=False,
        help_text='Contenido del archivo .json, o el archivo mismo si se envía como multipart.',
    )
    hostname = serializers.CharField(required=False)
    property_type = serializers.ChoiceField(choices=['DOMAIN', 'URL_PREFIX'], required=False)
    location = serializers.CharField(required=False)


class OnboardingView(BaseApiView):
    @extend_schema(
        responses=OnboardingStateSerializer,
        tags=['onboarding'],
        description=(
            'Estado del recorrido, con cada paso ya comprobado contra el estado real de la '
            'cuenta. No llama a Google: consultar el recorrido no gasta cuota.'
        ),
    )
    def get(self, request):
        return Response(OnboardingStateSerializer(services.state(request.user)).data)


class OnboardingStepVerifyView(BaseApiView):
    # Acepta el archivo de clave subido o pegado, igual que el alta de
    # credenciales: desde la pantalla lo natural es lo primero, desde un pipeline
    # lo segundo.
    parser_classes = [JSONParser, MultiPartParser, FormParser]

    @extend_schema(
        request=StepVerifySerializer,
        responses={200: OnboardingStateSerializer},
        tags=['onboarding'],
        description=(
            'Comprueba el requisito de un paso y, si hace falta, da de alta lo que el paso '
            'necesita. Reutiliza lo ya creado en vez de duplicarlo.'
        ),
    )
    def post(self, request, step):
        """
        Devuelve 200 aunque la comprobación no pase.

        Que un requisito todavía no esté cumplido es el resultado de la
        operación, no un error del pedido: contestar 4xx haría que un cliente
        trate «te falta autorizar la propiedad» como «armaste mal la petición».
        Lo que sí sale con 422 es un dato inválido —un identificador de proyecto
        con mayúsculas, un sitemap sin esquema—, porque ahí sí hay algo que
        corregir en lo enviado.
        """
        state = services.verify(request.user, step, _step_data(request))
        return Response(OnboardingStateSerializer(state).data)


def _step_data(request) -> dict:
    """Toma los campos del paso, con el archivo de clave venga como venga."""
    data = {key: value for key, value in request.data.items() if key != 'key_file'}

    if request.FILES.get('key_file') or request.data.get('key_file'):
        data['key_file'] = _received_material(request)

    return data


class OnboardingDismissView(BaseApiView):
    @extend_schema(
        request=None,
        responses={200: OnboardingStateSerializer},
        tags=['onboarding'],
        description=(
            'Omite el recorrido. No restringe ninguna funcionalidad: los formularios completos '
            'siguen ofreciendo las mismas operaciones, y el recorrido queda disponible para '
            'retomarlo.'
        ),
    )
    def post(self, request):
        return Response(OnboardingStateSerializer(services.dismiss(request.user)).data)
