"""Endpoints de la cuenta, sus claves de integración y sus sesiones abiertas."""

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

from apps.accounts.models import ApiKey
from apps.accounts.services import account_state, active_sessions, close_session, close_sessions
from apps.core.api import BaseApiView
from apps.core.errors import NotFound, ValidationFailed


class NoticeSerializer(serializers.Serializer):
    """
    Un aviso, sin texto.

    Lo que viaja es el código y los datos que su oración necesita. Quien consume
    la API ramifica por el código —que es estable y parte del contrato— en vez de
    comparar prosa, y la pantalla la escribe en el idioma de quien está mirando.
    """

    code = serializers.CharField()
    action_path = serializers.CharField()
    level = serializers.CharField()
    params = serializers.DictField()


class AccountStatusSerializer(serializers.Serializer):
    credential_status = serializers.CharField(allow_null=True)
    can_operate = serializers.BooleanField()
    domains_with_lost_access = serializers.IntegerField()
    onboarding_completed = serializers.BooleanField()
    display_timezone = serializers.CharField()
    notices = NoticeSerializer(many=True)


class AccountStatusView(BaseApiView):
    """
    Estado de la cuenta y avisos vigentes.

    Es el endpoint que hace posible no mentir: si la credencial del módulo dejó
    de servir, ningún dominio puede presentarse como operativo aunque su propio
    estado siga siéndolo. Un cliente que sólo mire el estado de sus dominios va
    a creer que todo anda mientras nada se actualiza.
    """

    @extend_schema(responses=AccountStatusSerializer, tags=['account'])
    def get(self, request):
        state = account_state(request.user).as_dict()
        return Response(AccountStatusSerializer(state).data)


class ApiKeySerializer(serializers.ModelSerializer):
    """
    Una clave, sin la clave.

    El valor completo se muestra una única vez, al crearla, y no vuelve a
    existir en ningún lado. Las revocadas se siguen listando: sirven para
    auditar qué se usó y hasta cuándo (FR-061).
    """

    id = serializers.UUIDField(read_only=True)
    is_revoked = serializers.BooleanField(read_only=True)

    class Meta:
        model = ApiKey
        fields = ['id', 'name', 'prefix', 'last_used_at', 'revoked_at', 'is_revoked', 'created_at']
        read_only_fields = ['prefix', 'last_used_at', 'revoked_at', 'created_at']


class ApiKeyCreatedSerializer(ApiKeySerializer):
    key = serializers.CharField(read_only=True)

    class Meta(ApiKeySerializer.Meta):
        fields = [*ApiKeySerializer.Meta.fields, 'key']


class ApiKeyListView(BaseApiView):
    @extend_schema(responses=ApiKeySerializer(many=True), tags=['api-keys'])
    def get(self, request):
        keys = ApiKey.objects.filter(account=request.user)
        return Response(ApiKeySerializer(keys, many=True).data)

    @extend_schema(
        request=ApiKeySerializer, responses={201: ApiKeyCreatedSerializer}, tags=['api-keys']
    )
    def post(self, request):
        name = (request.data.get('name') or '').strip()
        if not name:
            raise ValidationFailed(
                'La clave necesita un nombre para poder reconocerla.', field='name'
            )

        key, plaintext = ApiKey.issue(account=request.user, name=name)
        data = ApiKeySerializer(key).data
        data['key'] = plaintext
        return Response(data, status=status.HTTP_201_CREATED)


class ApiKeyDetailView(BaseApiView):
    @extend_schema(responses={204: None}, tags=['api-keys'])
    def delete(self, request, key_id):
        """Revoca la clave. No la borra: la fila queda para poder auditarla."""
        key = ApiKey.objects.filter(id=key_id, account=request.user).first()
        if key is None:
            raise NotFound('Esa clave no existe en esta cuenta.')

        key.revoke()
        return Response(status=status.HTTP_204_NO_CONTENT)


class SessionSerializer(serializers.Serializer):
    """
    Una sesión abierta, con lo que hace falta para decidir cuál cerrar (FR-059).

    La dirección de origen viaja tal como llegó. No se traduce a ciudad ni a
    país: la geolocalización por IP es una inferencia, y presentarla como dato
    cierto sería exactamente el tipo de afirmación que el producto no hace.
    """

    id = serializers.UUIDField()
    is_current = serializers.BooleanField()
    ip_address = serializers.CharField(allow_null=True)
    user_agent = serializers.CharField(allow_null=True)
    browser = serializers.CharField(allow_null=True)
    operating_system = serializers.CharField(allow_null=True)
    last_activity_at = serializers.DateTimeField()
    created_at = serializers.DateTimeField()


class SessionsClosedSerializer(serializers.Serializer):
    closed = serializers.IntegerField()


class SessionListView(BaseApiView):
    @extend_schema(
        operation_id='sessions_list',
        responses=SessionSerializer(many=True),
        tags=['sessions'],
    )
    def get(self, request):
        sessions = active_sessions(request.user, current_session_key=request.session.session_key)
        return Response(SessionSerializer([s.as_dict() for s in sessions], many=True).data)

    @extend_schema(
        operation_id='sessions_close_all',
        responses={200: SessionsClosedSerializer},
        tags=['sessions'],
    )
    def delete(self, request):
        """
        Cierra las sesiones de la cuenta y conserva la que hizo el pedido.

        Autenticado por clave de API no hay ninguna sesión de navegador en
        curso, así que no hay cuál conservar y las cierra todas. Es lo que se
        espera de un «cerrá todo» disparado desde un pipeline tras un incidente.
        """
        closed = close_sessions(request.user, keep_session_key=request.session.session_key)
        return Response({'closed': closed})


class SessionDetailView(BaseApiView):
    @extend_schema(operation_id='sessions_close', responses={204: None}, tags=['sessions'])
    def delete(self, request, session_id):
        """Cierra una sesión. Si es la que hizo el pedido, se cierra a sí misma."""
        session = close_session(request.user, session_id=session_id)

        # La sesión en curso ya quedó invalidada en el almacén; sin vaciarla acá
        # también, la respuesta la volvería a escribir y la cookie seguiría
        # entrando.
        if session.session_key == request.session.session_key:
            request.session.flush()

        return Response(status=status.HTTP_204_NO_CONTENT)
