"""
gamification/views.py
======================
Vistas del módulo de gamificación.

Separación de responsabilidades:
  - El engine calcula y escribe puntos/insignias
  - Las vistas solo leen resultados y orquestan canjes
"""

import logging
from django.utils import timezone
from django.db import transaction
from rest_framework import viewsets
from rest_framework.views import APIView
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated

from gamification.models import (
    Temporada, Puntos, Insignia, InsigniaAlumno,
    BeneficioFicha, Canje,
)
from gamification.serializers import (
    TemporadaSerializer, PuntosSerializer, InsigniaSerializer,
    InsigniaAlumnoSerializer, RankingItemSerializer,
    BeneficioFichaSerializer, CanjeSerializer, SolicitarCanjeSerializer,
)
from gamification.engine import GamificationEngine
from shared.permissions import IsApprovedDocente, IsAlumno, IsAdminOrDocente
from shared.utils.responses import success_response, error_response
from shared.pagination import StandardPagination

logger = logging.getLogger('eduplay')


class TemporadaViewSet(viewsets.ModelViewSet):
    """CRUD de temporadas. Solo docentes de la sección."""
    permission_classes = [IsApprovedDocente]
    serializer_class   = TemporadaSerializer

    def get_queryset(self):
        return Temporada.objects.filter(
            seccion__asignatura__owner=self.request.user
        ).select_related('seccion')

    @action(detail=True, methods=['post'])
    def cerrar(self, request, pk=None):
        """
        POST /gamification/temporadas/{id}/cerrar/
        Cierra la temporada y congela el podio actual.
        Idempotente: cerrar una temporada ya cerrada no falla.
        """
        temporada = self.get_object()
        if temporada.estado == 'cerrada':
            return error_response(message='La temporada ya está cerrada.', status_code=400)

        with transaction.atomic():
            temporada.estado = 'cerrada'
            temporada.cierre = timezone.now().date()
            temporada.save(update_fields=['estado', 'cierre'])

        return success_response(message='Temporada cerrada. El podio quedó congelado.')


class RankingView(APIView):
    """
    GET /gamification/ranking/
    Ranking de una sección/temporada ordenado por puntos.

    Query params:
      seccion_id (requerido)
      temporada_id (opcional — si no se da, muestra la activa)
      limit (opcional, default 20)
    """
    permission_classes = [IsAuthenticated]

    def get(self, request):
        seccion_id   = request.query_params.get('seccion_id')
        temporada_id = request.query_params.get('temporada_id')
        limit        = int(request.query_params.get('limit', 20))

        if not seccion_id:
            return error_response(message='seccion_id es requerido.', status_code=400)

        qs = Puntos.objects.filter(
            seccion_id=seccion_id
        ).select_related('alumno').order_by('-total_puntos')

        if temporada_id:
            qs = qs.filter(temporada_id=temporada_id)
        else:
            # Sin filtro de temporada: acumulado total de la sección
            qs = qs.filter(temporada__isnull=True)

        qs = qs[:limit]

        ranking = [
            {
                'pos':            i + 1,
                'alumno_id':      p.alumno_id,
                'nickname':       p.alumno.nickname or f'Alumno {p.alumno_id}',
                'total_puntos':   p.total_puntos,
                'total_fichas':   p.total_fichas,
                'racha_sesiones': p.racha_sesiones,
                'insignias_count': InsigniaAlumno.objects.filter(
                    alumno_id=p.alumno_id, seccion_id=seccion_id
                ).count(),
            }
            for i, p in enumerate(qs)
        ]

        return success_response(data=ranking)


class InsigniaViewSet(viewsets.ReadOnlyModelViewSet):
    """
    Catálogo de todas las insignias disponibles.
    Cualquier usuario autenticado puede consultarlo.
    """
    permission_classes = [IsAuthenticated]
    serializer_class   = InsigniaSerializer
    pagination_class   = StandardPagination

    def get_queryset(self):
        qs = Insignia.objects.all()
        categoria = self.request.query_params.get('categoria')
        if categoria:
            qs = qs.filter(categoria=categoria)
        return qs


class MisInsigniasView(APIView):
    """
    GET /gamification/mis-insignias/?seccion_id=N
    Insignias del alumno autenticado en una sección.
    """
    permission_classes = [IsAlumno]

    def get(self, request):
        seccion_id = request.query_params.get('seccion_id')
        if not seccion_id:
            return error_response(message='seccion_id es requerido.', status_code=400)

        insignias = InsigniaAlumno.objects.filter(
            alumno=request.user,
            seccion_id=seccion_id
        ).select_related('insignia').order_by('-created_at')

        serializer = InsigniaAlumnoSerializer(insignias, many=True)
        return success_response(data=serializer.data)


class BeneficioFichaViewSet(viewsets.ModelViewSet):
    """
    CRUD de beneficios canjeables con fichas.
    Docente crea/edita; alumnos solo leen.
    """
    serializer_class = BeneficioFichaSerializer

    def get_permissions(self):
        if self.action in ('list', 'retrieve'):
            return [IsAuthenticated()]
        return [IsApprovedDocente()]

    def get_queryset(self):
        qs = BeneficioFicha.objects.all()
        seccion_id = self.request.query_params.get('seccion_id')
        if seccion_id:
            qs = qs.filter(seccion_id=seccion_id)
        if self.request.user.role == 'docente':
            qs = qs.filter(seccion__asignatura__owner=self.request.user)
        return qs.filter(disponible=True)


class CanjeViewSet(viewsets.ReadOnlyModelViewSet):
    """
    Historial de canjes.
    Docente ve los de sus secciones; alumno ve los propios.
    """
    serializer_class = CanjeSerializer
    pagination_class = StandardPagination

    def get_permissions(self):
        return [IsAuthenticated()]

    def get_queryset(self):
        user = self.request.user
        if user.role == 'alumno':
            return Canje.objects.filter(alumno=user).select_related('beneficio')
        if user.role == 'docente':
            return Canje.objects.filter(
                seccion__asignatura__owner=user
            ).select_related('alumno', 'beneficio')
        return Canje.objects.none()

    @action(detail=True, methods=['post'], permission_classes=[IsApprovedDocente])
    def aprobar(self, request, pk=None):
        """POST /gamification/canjes/{id}/aprobar/"""
        canje = self.get_object()
        if canje.estado != 'pendiente':
            return error_response(message='Solo se pueden aprobar canjes pendientes.', status_code=400)

        with transaction.atomic():
            canje.estado      = 'aprobado'
            canje.aprobado_por = request.user
            canje.aprobado_en  = timezone.now()
            canje.save(update_fields=['estado', 'aprobado_por', 'aprobado_en'])

        return success_response(message='Canje aprobado.')

    @action(detail=True, methods=['post'], permission_classes=[IsApprovedDocente])
    def rechazar(self, request, pk=None):
        """POST /gamification/canjes/{id}/rechazar/ — devuelve las fichas al alumno."""
        canje = self.get_object()
        if canje.estado != 'pendiente':
            return error_response(message='Solo se pueden rechazar canjes pendientes.', status_code=400)

        with transaction.atomic():
            # Devolver fichas
            GamificationEngine.ajustar_fichas(
                alumno=canje.alumno,
                seccion=canje.seccion,
                delta=+canje.fichas_usadas,
                motivo=f'Devolución por canje rechazado #{canje.id}',
                docente=request.user,
            )
            canje.estado = 'rechazado'
            canje.save(update_fields=['estado'])

        return success_response(message='Canje rechazado. Las fichas fueron devueltas al alumno.')


class SolicitarCanjeView(APIView):
    """
    POST /gamification/canjear/
    Alumno solicita canjear un beneficio con sus fichas.
    """
    permission_classes = [IsAlumno]

    def post(self, request):
        serializer = SolicitarCanjeSerializer(
            data=request.data, context={'request': request}
        )
        if not serializer.is_valid():
            return error_response(
                message='Solicitud inválida', errors=serializer.errors, status_code=400
            )

        beneficio  = serializer.context['beneficio']
        alumno     = request.user
        seccion    = beneficio.seccion

        # Verificar fichas suficientes
        puntos_obj = Puntos.objects.filter(
            alumno=alumno, seccion=seccion
        ).first()

        if not puntos_obj or puntos_obj.total_fichas < beneficio.costo_fichas:
            return error_response(
                message=f'Fichas insuficientes. '
                        f'Necesitas {beneficio.costo_fichas}, tienes '
                        f'{getattr(puntos_obj, "total_fichas", 0)}.',
                status_code=400
            )

        with transaction.atomic():
            # Descontar fichas
            GamificationEngine.ajustar_fichas(
                alumno=alumno,
                seccion=seccion,
                delta=-beneficio.costo_fichas,
                motivo=f'Canje de "{beneficio.nombre}"',
                docente=seccion.docente,  # Registro interno
            )
            canje = Canje.objects.create(
                alumno=alumno,
                beneficio=beneficio,
                seccion=seccion,
                fichas_usadas=beneficio.costo_fichas,
            )

        return success_response(
            data={'canje_id': canje.id, 'estado': canje.estado},
            message=f'Canje solicitado. El docente debe aprobarlo.',
            status_code=201
        )


class AjustarFichasView(APIView):
    """
    POST /gamification/ajustar-fichas/
    Docente ajusta fichas manualmente (+/–) con un motivo.
    """
    permission_classes = [IsApprovedDocente]

    def post(self, request):
        alumno_id  = request.data.get('alumno_id')
        seccion_id = request.data.get('seccion_id')
        delta      = request.data.get('delta')
        motivo     = request.data.get('motivo', '')

        if not all([alumno_id, seccion_id, delta is not None, motivo]):
            return error_response(
                message='Campos requeridos: alumno_id, seccion_id, delta, motivo',
                status_code=400
            )

        from django.contrib.auth import get_user_model
        User = get_user_model()

        try:
            alumno  = User.objects.get(pk=alumno_id, role='alumno')
            from academic.models import Seccion
            seccion = Seccion.objects.get(pk=seccion_id, asignatura__owner=request.user)
        except (User.DoesNotExist, Exception):
            return error_response(message='Alumno o sección no encontrados.', status_code=404)

        try:
            nuevo_total = GamificationEngine.ajustar_fichas(
                alumno=alumno, seccion=seccion,
                delta=int(delta), motivo=motivo, docente=request.user
            )
        except ValueError as e:
            return error_response(message=str(e), status_code=400)

        return success_response(
            data={'nuevo_total_fichas': nuevo_total},
            message='Fichas ajustadas correctamente.'
        )
