"""
academic/models.py
===================
Modelos de estructura académica: Asignaturas, Secciones, Inscripciones,
Grupos de alumnos y Actividades de Exposición.

Jerarquía:
  Asignatura (docente)
    └── Sección (paralelo)
         ├── Inscripcion (alumno ↔ sección)
         ├── Grupo (conjunto de alumnos)
         │    └── ActividadExposicion (actividad donde expone el grupo)
         └── CodigoAcceso (código efímero para unirse)
"""

from django.db import models
from django.conf import settings
from django.core.validators import MinLengthValidator

from shared.mixins import TimestampedModel, SoftDeleteModel, OwnedModel
from shared.validators.fields import validate_hex_color
from shared.utils.tokens import TokenService


class Asignatura(TimestampedModel, SoftDeleteModel, OwnedModel):
    """
    Asignatura creada por un docente.
    Una asignatura puede tener múltiples secciones (paralelos).
    
    Ej: "Programación Frontend" → Curso 1, Curso 2, Sección Tarde
    """

    class Color(models.TextChoices):
        """Colores predefinidos para identificar asignaturas visualmente."""
        BLUE   = 'blue',   'Azul'
        PURPLE = 'purple', 'Morado'
        GREEN  = 'green',  'Verde'
        PINK   = 'pink',   'Rosa'
        GOLD   = 'gold',   'Dorado'

    nombre      = models.CharField(max_length=200, verbose_name='Nombre')
    codigo      = models.CharField(
        max_length=20,
        validators=[MinLengthValidator(2)],
        verbose_name='Código',
        help_text='Código corto sin espacios. Ej: TI2031'
    )
    descripcion = models.TextField(blank=True, verbose_name='Descripción')
    color       = models.CharField(
        max_length=10,
        choices=Color.choices,
        default=Color.BLUE,
        verbose_name='Color identificador'
    )
    es_publica = models.BooleanField(
        default=False,
        verbose_name='Visible para otros docentes',
        help_text='Si True, otros docentes pueden ver las preguntas públicas de esta asignatura'
    )

    class Meta(TimestampedModel.Meta):
        verbose_name = 'Asignatura'
        verbose_name_plural = 'Asignaturas'
        db_table = 'academic_asignatura'
        # Un docente no puede tener dos asignaturas con el mismo código
        unique_together = [['owner', 'codigo']]

    def __str__(self):
        return f'{self.codigo} — {self.nombre}'

    @property
    def total_alumnos(self) -> int:
        """Total de alumnos inscritos en todas las secciones."""
        return Inscripcion.objects.filter(
            seccion__asignatura=self,
            activo=True
        ).values('alumno').distinct().count()

    @property
    def total_secciones(self) -> int:
        return self.secciones.filter(deleted_at__isnull=True).count()


class Seccion(TimestampedModel, SoftDeleteModel):
    """
    Sección (paralelo) de una asignatura.
    Es el nivel donde interactúan los alumnos con el docente.
    
    Maneja su propio código efímero de acceso para que alumnos puedan unirse.
    """

    asignatura = models.ForeignKey(
        Asignatura,
        on_delete=models.CASCADE,
        related_name='secciones',
        verbose_name='Asignatura'
    )
    nombre  = models.CharField(max_length=100, verbose_name='Nombre')
    periodo = models.CharField(
        max_length=20,
        verbose_name='Período',
        help_text='Ej: 2026-1, 2026-2'
    )
    capacidad_maxima = models.PositiveSmallIntegerField(
        null=True, blank=True,
        verbose_name='Capacidad máxima',
        help_text='Si está definida, no se permiten más inscripciones'
    )

    # ── Código de acceso efímero ──
    codigo_acceso = models.CharField(
        max_length=8,
        blank=True, null=True,
        verbose_name='Código de acceso activo'
    )
    codigo_expira = models.DateTimeField(
        null=True, blank=True,
        verbose_name='Expiración del código de acceso'
    )

    class Meta(TimestampedModel.Meta):
        verbose_name = 'Sección'
        verbose_name_plural = 'Secciones'
        db_table = 'academic_seccion'
        unique_together = [['asignatura', 'nombre', 'periodo']]

    def __str__(self):
        return f'{self.asignatura.codigo} — {self.nombre} ({self.periodo})'

    @property
    def docente(self):
        """Shortcut al docente propietario de la asignatura."""
        return self.asignatura.owner

    @property
    def total_alumnos(self) -> int:
        return self.inscripciones.filter(activo=True).count()

    @property
    def codigo_activo(self) -> bool:
        """True si el código de acceso actual es válido (no expiró)."""
        from django.utils import timezone
        return (
            self.codigo_acceso is not None and
            self.codigo_expira is not None and
            timezone.now() < self.codigo_expira
        )

    def generar_codigo_acceso(self) -> str:
        """
        Genera un nuevo código de acceso efímero para esta sección.
        El código anterior se invalida automáticamente.
        Expira en SECURITY_CONFIG['ACCESS_CODE_EXPIRY_MINUTES'] minutos.
        """
        from django.utils import timezone
        from datetime import timedelta

        expiry_minutes = settings.SECURITY_CONFIG['ACCESS_CODE_EXPIRY_MINUTES']
        self.codigo_acceso = TokenService.generate_access_code()
        self.codigo_expira = timezone.now() + timedelta(minutes=expiry_minutes)
        self.save(update_fields=['codigo_acceso', 'codigo_expira'])
        return self.codigo_acceso

    def invalidar_codigo(self):
        """Invalida el código de acceso actual (ej: al cerrar el período)."""
        self.codigo_acceso = None
        self.codigo_expira = None
        self.save(update_fields=['codigo_acceso', 'codigo_expira'])


class Inscripcion(TimestampedModel):
    """
    Relación alumno ↔ sección.
    Registra cuándo y cómo el alumno se unió a la sección.
    
    Un alumno puede estar inscrito en múltiples secciones
    (de distintos docentes/asignaturas).
    """

    alumno  = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='inscripciones',
        verbose_name='Alumno'
    )
    seccion = models.ForeignKey(
        Seccion,
        on_delete=models.CASCADE,
        related_name='inscripciones',
        verbose_name='Sección'
    )
    activo  = models.BooleanField(
        default=True,
        verbose_name='Inscripción activa',
        help_text='False si el alumno fue removido de la sección'
    )

    class Meta(TimestampedModel.Meta):
        verbose_name = 'Inscripción'
        verbose_name_plural = 'Inscripciones'
        db_table = 'academic_inscripcion'
        unique_together = [['alumno', 'seccion']]

    def __str__(self):
        return f'{self.alumno_id} en {self.seccion}'


class Grupo(TimestampedModel):
    """
    Grupo de alumnos dentro de una sección.
    Se usa principalmente para sesiones de exposición.
    
    Un grupo puede participar en múltiples actividades.
    Los integrantes pueden cambiar entre actividades.
    """

    seccion = models.ForeignKey(
        Seccion,
        on_delete=models.CASCADE,
        related_name='grupos',
        verbose_name='Sección'
    )
    nombre = models.CharField(max_length=200, verbose_name='Nombre del grupo')

    # Color hexadecimal libre (#RRGGBB) — cualquier color del espectro
    color = models.CharField(
        max_length=7,
        default='#00d4ff',
        validators=[validate_hex_color],
        verbose_name='Color identificador',
        help_text='Color hexadecimal #RRGGBB para identificar el grupo visualmente'
    )

    integrantes = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='grupos',
        blank=True,
        verbose_name='Integrantes',
        help_text='Alumnos que forman parte de este grupo'
    )

    class Meta(TimestampedModel.Meta):
        verbose_name = 'Grupo'
        verbose_name_plural = 'Grupos'
        db_table = 'academic_grupo'

    def __str__(self):
        return f'{self.nombre} ({self.seccion})'

    @property
    def total_integrantes(self) -> int:
        return self.integrantes.count()


class ActividadExposicion(TimestampedModel):
    """
    Actividad de exposición donde grupos presentan frente a la clase.
    
    Una actividad pertenece a una sección y agrupa múltiples grupos.
    Al abrir una sesión de tipo 'exposicion', se vincula a esta actividad.
    
    Un grupo puede aparecer en múltiples actividades con los mismos
    o distintos integrantes (los integrantes pertenecen al Grupo, no a la Actividad).
    """

    class Estado(models.TextChoices):
        PENDIENTE = 'pendiente', 'Pendiente'
        ACTIVA    = 'activa',    'Activa'
        CERRADA   = 'cerrada',   'Cerrada'

    seccion     = models.ForeignKey(
        Seccion,
        on_delete=models.CASCADE,
        related_name='actividades_exposicion',
        verbose_name='Sección'
    )
    nombre      = models.CharField(max_length=200, verbose_name='Nombre')
    descripcion = models.TextField(blank=True, verbose_name='Descripción')
    fecha       = models.DateField(null=True, blank=True, verbose_name='Fecha programada')
    estado      = models.CharField(
        max_length=10,
        choices=Estado.choices,
        default=Estado.PENDIENTE,
        verbose_name='Estado'
    )

    grupos = models.ManyToManyField(
        Grupo,
        related_name='actividades',
        blank=True,
        verbose_name='Grupos participantes'
    )

    class Meta(TimestampedModel.Meta):
        verbose_name = 'Actividad de exposición'
        verbose_name_plural = 'Actividades de exposición'
        db_table = 'academic_actividadexposicion'
        ordering = ['-fecha', '-created_at']

    def __str__(self):
        return f'{self.nombre} — {self.seccion}'
