"""
sessions/views.py
==================
Vistas HTTP del módulo de sesiones en vivo.

Arquitectura HTTP + WebSocket:
  HTTP REST  → CRUD de sesiones, activar preguntas, enviar respuestas, gestionar dudas
  WebSocket  → push de eventos al alumno (pregunta activa, puntos, insignias)

Flujo de activar una pregunta (el más crítico):
  1. Docente  → POST /sesiones/{id}/activar-pregunta/
  2. Vista    → crea PreguntaActiva en BD
  3. Vista    → channel_layer.group_send() → WebSocket push a todos
  4. Alumno   → recibe pregunta en tiempo real
  5. Alumno   → POST /sessions/responder/
  6. Vista    → evalúa + GamificationEngine
  7. Vista    → notifica resultado al alumno por WS (canal privado)
"""

import logging
from datetime import timedelta
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.db import transaction
from django.utils import timezone
from rest_framework import viewsets
from rest_framework.views import APIView
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated

from sessions.models import Sesion, PreguntaActiva, Respuesta, DudaAlumno
from sessions.serializers import (
    SesionSerializer, SesionCreateSerializer,
    PreguntaActivaPublicaSerializer, PreguntaActivaResultadoSerializer,
    EnviarRespuestaSerializer,
    DudaAlumnoSerializer, EnviarDudaSerializer, ResponderDudaSerializer,
)
from gamification.engine import GamificationEngine
from shared.permissions import IsApprovedDocente, IsAlumno
from shared.utils.responses import success_response, error_response
from shared.pagination import StandardPagination, SmallPagination

logger       = logging.getLogger('eduplay')
channel_layer = get_channel_layer()


def _ws_send(group: str, event: dict):
    """Envía un evento a un grupo WebSocket desde código síncrono."""
    try:
        async_to_sync(channel_layer.group_send)(group, event)
    except Exception as e:
        logger.warning(f'WS send falló para grupo {group}: {e}')


class SesionViewSet(viewsets.ModelViewSet):
    """
    ViewSet principal de sesiones.
    Incluye acciones para todo el ciclo de vida de la sesión.
    """
    pagination_class = StandardPagination

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

    def get_queryset(self):
        user = self.request.user
        qs   = Sesion.objects.select_related('seccion', 'docente', 'temporada')
        if user.role == 'docente':
            qs = qs.filter(docente=user)
        elif user.role == 'alumno':
            qs = qs.filter(
                seccion__inscripciones__alumno=user,
                seccion__inscripciones__activo=True,
            ).distinct()
        estado = self.request.query_params.get('estado')
        if estado:
            qs = qs.filter(estado=estado)
        seccion_id = self.request.query_params.get('seccion_id')
        if seccion_id:
            qs = qs.filter(seccion_id=seccion_id)
        return qs

    def get_serializer_class(self):
        if self.action in ('create', 'update', 'partial_update'):
            return SesionCreateSerializer
        return SesionSerializer

    def perform_create(self, serializer):
        serializer.save(docente=self.request.user)

    @action(detail=True, methods=['post'])
    def abrir(self, request, pk=None):
        """Abre la sesión (esperando → activa)."""
        sesion = self.get_object()
        if sesion.docente != request.user:
            return error_response(message='Solo el docente propietario puede abrir la sesión.', status_code=403)
        if sesion.estado != 'esperando':
            return error_response(message=f'Estado actual: "{sesion.estado}". Solo se abre desde "esperando".', status_code=400)
        sesion.abrir()
        _ws_send(f'session_{sesion.id}', {'type': 'sesion_abierta', 'data': {}})
        logger.info(f'Sesión abierta: id={sesion.id}')
        return success_response(data=SesionSerializer(sesion).data, message='Sesión abierta.')

    @action(detail=True, methods=['post'])
    def cerrar(self, request, pk=None):
        """Cierra la sesión, actualiza rachas y notifica por WS."""
        sesion = self.get_object()
        if sesion.docente != request.user:
            return error_response(message='Solo el docente propietario puede cerrar la sesión.', status_code=403)
        if sesion.estado != 'activa':
            return error_response(message='Solo se puede cerrar una sesión activa.', status_code=400)

        with transaction.atomic():
            sesion.cerrar()
            alumnos = sesion.seccion.inscripciones.filter(activo=True).values_list('alumno', flat=True)
            from django.contrib.auth import get_user_model
            User = get_user_model()
            for aid in alumnos:
                try:
                    GamificationEngine.actualizar_racha(
                        User.objects.get(pk=aid), sesion.seccion, sesion.cerrada_en.date()
                    )
                except Exception as e:
                    logger.warning(f'Error actualizando racha alumno={aid}: {e}')

        _ws_send(f'session_{sesion.id}', {'type': 'sesion_cerrada', 'data': {}})
        logger.info(f'Sesión cerrada: id={sesion.id} duración={sesion.duracion_minutos} min')
        return success_response(data={'duracion_minutos': sesion.duracion_minutos}, message='Sesión cerrada.')

    @action(detail=True, methods=['post'])
    def activar_pregunta(self, request, pk=None):
        """Activa una pregunta: cierra la anterior, crea PreguntaActiva, push WS."""
        sesion = self.get_object()
        if sesion.estado != 'activa':
            return error_response(message='La sesión debe estar activa.', status_code=400)

        pregunta_id = request.data.get('pregunta_id')
        if not pregunta_id:
            return error_response(message='pregunta_id requerido.', status_code=400)

        PreguntaActiva.objects.filter(sesion=sesion, estado='activa').update(estado='cerrada')

        try:
            from questions.models import Pregunta
            pregunta = Pregunta.objects.get(pk=pregunta_id)
        except Pregunta.DoesNotExist:
            return error_response(message='Pregunta no encontrada.', status_code=404)

        orden = sesion.preguntas_activas.count() + 1
        pa = PreguntaActiva.objects.create(
            sesion=sesion, pregunta=pregunta, orden=orden,
            expira_en=timezone.now() + timedelta(seconds=pregunta.tiempo_seg),
        )

        import random
        pares = list(pregunta.pares or [])
        random.shuffle(pares)

        _ws_send(f'session_{sesion.id}', {
            'type': 'pregunta_activada',
            'data': {
                'pregunta_activa_id': pa.id,
                'tipo':       pregunta.tipo,
                'enunciado':  pregunta.enunciado,
                'tiempo_seg': pregunta.tiempo_seg,
                'orden':      orden,
                'opciones':   [{'texto': o.get('texto', '')} for o in (pregunta.opciones or [])],
                'pares':      pares if pregunta.tipo == 'arrastrar' else None,
            },
        })

        logger.info(f'Pregunta activada: pa_id={pa.id} sesion={sesion.id}')
        return success_response(data=PreguntaActivaPublicaSerializer(pa).data, message='Pregunta activada.', status_code=201)

    @action(detail=True, methods=['post'])
    def cerrar_pregunta(self, request, pk=None):
        """Cierra manualmente la pregunta activa."""
        sesion = self.get_object()
        pa = PreguntaActiva.objects.filter(sesion=sesion, estado='activa').first()
        if not pa:
            return error_response(message='No hay pregunta activa.', status_code=400)
        pa.cerrar()
        _ws_send(f'session_{sesion.id}', {'type': 'pregunta_cerrada', 'pregunta_activa_id': pa.id})
        return success_response(data=PreguntaActivaResultadoSerializer(pa).data)

    @action(detail=True, methods=['get'])
    def estadisticas(self, request, pk=None):
        """Estadísticas en tiempo real de la sesión para el dashboard del docente."""
        sesion = self.get_object()
        pa = PreguntaActiva.objects.filter(sesion=sesion, estado='activa').first()
        return success_response(data={
            'total_alumnos':      sesion.seccion.total_alumnos,
            'pregunta_activa':    PreguntaActivaPublicaSerializer(pa).data if pa else None,
            'respuestas_recibidas': Respuesta.objects.filter(pregunta_activa=pa).count() if pa else 0,
        })

    @action(detail=True, methods=['post'])
    def cambiar_grupo(self, request, pk=None):
        """(Exposición) Cambia el grupo activo y notifica por WS."""
        sesion = self.get_object()
        if sesion.tipo != 'exposicion':
            return error_response(message='Solo aplica a sesiones de exposición.', status_code=400)
        from academic.models import Grupo
        try:
            grupo = Grupo.objects.get(pk=request.data.get('grupo_id'), seccion=sesion.seccion)
        except Grupo.DoesNotExist:
            return error_response(message='Grupo no encontrado.', status_code=404)
        sesion.grupo_activo = grupo
        sesion.save(update_fields=['grupo_activo'])
        _ws_send(f'session_{sesion.id}', {
            'type': 'grupo_cambiado',
            'data': {'grupo_id': grupo.id, 'grupo_nombre': grupo.nombre, 'color': grupo.color},
        })
        return success_response(message=f'Grupo activo: {grupo.nombre}')


class ResponderPreguntaView(APIView):
    """
    POST /sessions/responder/
    Alumno envía su respuesta a una pregunta activa.
    El tiempo se calcula en el servidor para prevenir trampa.
    """
    permission_classes = [IsAlumno]

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

        pa     = serializer.context['pregunta_activa']
        alumno = request.user

        if Respuesta.objects.filter(pregunta_activa=pa, alumno=alumno).exists():
            return error_response(message='Ya respondiste esta pregunta.', status_code=409)

        tiempo_ms     = int((timezone.now() - pa.activada_en).total_seconds() * 1000)
        es_sospechosa = tiempo_ms < 1500
        es_correcta   = _evaluar_correcto(pa.pregunta, serializer.validated_data)

        with transaction.atomic():
            respuesta = Respuesta.objects.create(
                pregunta_activa  = pa,
                alumno           = alumno,
                opcion_elegida   = serializer.validated_data.get('opcion_elegida'),
                respuesta_vf     = serializer.validated_data.get('respuesta_vf'),
                texto_respuesta  = serializer.validated_data.get('texto_respuesta', ''),
                respuesta_json   = serializer.validated_data.get('respuesta_json'),
                es_correcta      = es_correcta,
                tiempo_ms        = tiempo_ms,
                es_sospechosa    = es_sospechosa,
            )
            resultado = GamificationEngine.procesar_respuesta(respuesta)

        # Notificar al alumno por canal privado
        _ws_send(f'student_{alumno.id}', {
            'type': 'puntos_actualizados',
            'data': {
                'puntos_ganados': resultado.puntos_ganados,
                'es_correcta':    resultado.es_correcta,
                'fichas_nuevas':  resultado.fichas_nuevas,
                'nueva_racha':    resultado.nueva_racha,
            },
        })
        for insignia in resultado.insignias_nuevas:
            _ws_send(f'student_{alumno.id}', {'type': 'insignia_desbloqueada', 'data': insignia})

        # Notificar al docente (contador de respuestas)
        _ws_send(f'session_{pa.sesion_id}_teacher', {
            'type': 'respuesta_recibida',
            'data': {'alumno_id': alumno.id, 'es_correcta': es_correcta, 'es_sospechosa': es_sospechosa},
        })

        return success_response(
            data={
                'puntos_ganados':   resultado.puntos_ganados,
                'es_correcta':      resultado.es_correcta,
                'fichas_nuevas':    resultado.fichas_nuevas,
                'insignias_nuevas': resultado.insignias_nuevas,
            },
            message='Respuesta registrada.',
            status_code=201
        )


class DudaViewSet(viewsets.ModelViewSet):
    """Dudas enviadas por alumnos durante la sesión."""
    pagination_class = SmallPagination

    def get_permissions(self):
        if self.action == 'create':
            return [IsAlumno()]
        return [IsAuthenticated()]

    def get_serializer_class(self):
        if self.action == 'responder':
            return ResponderDudaSerializer
        return DudaAlumnoSerializer

    def get_queryset(self):
        user = self.request.user
        qs   = DudaAlumno.objects.select_related('alumno', 'sesion')
        sesion_id = self.request.query_params.get('sesion_id')
        if sesion_id:
            qs = qs.filter(sesion_id=sesion_id)
        if user.role == 'alumno':
            from django.db.models import Q
            qs = qs.filter(Q(alumno=user) | Q(es_anonima=False))
        return qs

    def create(self, request, *args, **kwargs):
        serializer = EnviarDudaSerializer(data=request.data, context={'request': request})
        if not serializer.is_valid():
            return error_response(message='Datos inválidos', errors=serializer.errors, status_code=400)

        sesion = serializer.context.get('sesion')
        if not sesion:
            try:
                sesion = Sesion.objects.get(pk=request.data.get('sesion_id'), estado='activa')
            except Sesion.DoesNotExist:
                return error_response(message='Sesión no activa.', status_code=400)

        duda = DudaAlumno.objects.create(
            sesion=sesion, alumno=request.user,
            texto=serializer.validated_data['texto'],
            es_anonima=serializer.validated_data.get('es_anonima', False),
        )

        _ws_send(f'session_{sesion.id}_teacher', {
            'type': 'nueva_duda',
            'data': {
                'duda_id':   duda.id,
                'texto':     duda.texto,
                'alumno_id': duda.alumno_id,
                'nickname':  duda.alumno.nickname,
                'anonima':   duda.es_anonima,
            },
        })
        return success_response(
            data=DudaAlumnoSerializer(duda, context={'request': request}).data,
            message='Pregunta enviada.',
            status_code=201
        )

    @action(detail=True, methods=['post'], permission_classes=[IsApprovedDocente])
    def responder(self, request, pk=None):
        """Docente responde una duda y la agrega al FAQ."""
        duda = self.get_object()
        serializer = ResponderDudaSerializer(data=request.data)
        if not serializer.is_valid():
            return error_response(message='Datos inválidos', errors=serializer.errors, status_code=400)
        duda.respuesta_docente = serializer.validated_data['respuesta']
        duda.respondida = True
        duda.save(update_fields=['respuesta_docente', 'respondida'])
        return success_response(message='Respuesta guardada.')

    @action(detail=True, methods=['post'], permission_classes=[IsApprovedDocente])
    def destacar(self, request, pk=None):
        duda = self.get_object()
        duda.destacada = not duda.destacada
        duda.save(update_fields=['destacada'])
        return success_response(data={'destacada': duda.destacada})

    @action(detail=True, methods=['post'], permission_classes=[IsAlumno])
    def votar(self, request, pk=None):
        duda = self.get_object()
        duda.votos += 1
        duda.save(update_fields=['votos'])
        return success_response(data={'votos': duda.votos})


# ── Helpers privados ──

def _evaluar_correcto(pregunta, data: dict):
    """Evalúa si la respuesta enviada es correcta. Retorna bool o None."""
    tipo = pregunta.tipo
    if tipo == 'multiple':
        idx  = data.get('opcion_elegida')
        opts = pregunta.opciones or []
        return opts[idx].get('correcta', False) if idx is not None and idx < len(opts) else False
    if tipo == 'vf':
        return data.get('respuesta_vf') == pregunta.correcta_vf
    if tipo == 'corta':
        texto  = (data.get('texto_respuesta') or '').lower().strip()
        claves = [c.lower().strip() for c in (pregunta.palabras_clave or [])]
        return any(c in texto for c in claves)
    if tipo == 'arrastrar':
        resp   = data.get('respuesta_json') or []
        pares  = pregunta.pares or []
        modo   = pregunta.modo_arrastrar or 'asociar'
        if len(pares) != len(resp):
            return False
        if modo == 'ordenar':
            return [p.get('izq') for p in pares] == [r.get('izq') for r in resp]
        mapa = {p.get('izq'): p.get('der') for p in pares}
        return all(mapa.get(r.get('izq')) == r.get('der') for r in resp)
    return None  # confuso / minuto — sin corrección automática
