import secrets

from django.db import models
from django.core.exceptions import ValidationError
from django.core.validators import FileExtensionValidator


def validar_tamano_escudo(archivo):
    limite_mb = 2
    if archivo.size > limite_mb * 1024 * 1024:
        raise ValidationError(f"La imagen no debe superar los {limite_mb} MB.")


VALIDADORES_ESCUDO = [
    FileExtensionValidator(allowed_extensions=["png", "jpg", "jpeg", "webp", "svg"]),
    validar_tamano_escudo,
]


def ruta_escudo_facultad(instance, filename):
    return f"escudos/facultades/{instance.id}_{filename}"


def ruta_escudo_carrera(instance, filename):
    return f"escudos/carreras/{instance.id}_{filename}"


# =============================================================================
# Jerarquia academica: Facultad -> Carrera -> Asignatura -> Grupo (+ Gestion)
# Controlada por una unica "Unidad Encargada" (no hay administracion delegada
# por facultad/carrera en esta fase).
# =============================================================================

class Facultad(models.Model):
    nombre = models.CharField(max_length=150, unique=True)
    escudo = models.ImageField(upload_to=ruta_escudo_facultad, null=True, blank=True, validators=VALIDADORES_ESCUDO)

    class Meta:
        ordering = ["nombre"]

    def __str__(self):
        return self.nombre


class Carrera(models.Model):
    nombre = models.CharField(max_length=150)
    facultad = models.ForeignKey(Facultad, on_delete=models.CASCADE, related_name="carreras")
    escudo = models.ImageField(upload_to=ruta_escudo_carrera, null=True, blank=True, validators=VALIDADORES_ESCUDO)

    class Meta:
        unique_together = ("nombre", "facultad")
        ordering = ["facultad__nombre", "nombre"]

    def __str__(self):
        return f"{self.nombre} ({self.facultad.nombre})"


class Gestion(models.Model):
    """Periodo academico, ej: '2026-II'. Se crea una vez y aplica a todo el sistema."""
    nombre = models.CharField(max_length=20, unique=True)
    fecha_inicio = models.DateField()
    fecha_fin = models.DateField()
    activa = models.BooleanField(default=True)

    class Meta:
        ordering = ["-fecha_inicio"]

    def __str__(self):
        return self.nombre

    def clean(self):
        if self.fecha_inicio and self.fecha_fin and self.fecha_inicio >= self.fecha_fin:
            raise ValidationError("La fecha de inicio debe ser anterior a la fecha de fin.")


class Asignatura(models.Model):
    nombre = models.CharField(max_length=150)
    carrera = models.ForeignKey(Carrera, on_delete=models.CASCADE, related_name="asignaturas")

    class Meta:
        unique_together = ("nombre", "carrera")
        ordering = ["carrera__nombre", "nombre"]

    def __str__(self):
        return f"{self.nombre} ({self.carrera.nombre})"


class Persona(models.Model):
    """Docentes y directores de carrera comparten esta tabla, diferenciados por rol."""
    ROL_DOCENTE = "DOCENTE"
    ROL_DIRECTOR = "DIRECTOR"
    ROLES = [(ROL_DOCENTE, "Docente"), (ROL_DIRECTOR, "Director de carrera")]

    nombre = models.CharField(max_length=150)
    email = models.EmailField()
    rol = models.CharField(max_length=10, choices=ROLES)

    class Meta:
        unique_together = ("email", "rol")
        ordering = ["nombre"]
        verbose_name_plural = "Personas"

    def __str__(self):
        return f"{self.nombre} ({self.get_rol_display()})"


class Grupo(models.Model):
    """
    Un grupo (paralelo) de una asignatura en una gestion, con su docente y director.

    IMPORTANTE: no se almacena la lista de estudiantes ni ningun dato que los
    identifique individualmente. Solo se guarda la CANTIDAD, suficiente para
    generar un QR anonimo por estudiante y preservar el anonimato del voto.
    """
    asignatura = models.ForeignKey(Asignatura, on_delete=models.CASCADE, related_name="grupos")
    gestion = models.ForeignKey(Gestion, on_delete=models.CASCADE, related_name="grupos")
    codigo = models.CharField(max_length=10, help_text="Ej: G1, G2")
    docente = models.ForeignKey(
        Persona, on_delete=models.PROTECT, related_name="grupos_dictados",
        limit_choices_to={"rol": Persona.ROL_DOCENTE},
    )
    director = models.ForeignKey(
        Persona, on_delete=models.PROTECT, related_name="grupos_supervisados",
        limit_choices_to={"rol": Persona.ROL_DIRECTOR},
    )
    cantidad_estudiantes = models.PositiveIntegerField(
        help_text="Cantidad de estudiantes matriculados (no se registra su identidad)."
    )

    class Meta:
        unique_together = ("asignatura", "gestion", "codigo")
        ordering = ["-gestion__fecha_inicio", "asignatura__nombre", "codigo"]

    def __str__(self):
        return f"{self.asignatura.nombre} - {self.codigo} ({self.gestion.nombre})"


# =============================================================================
# Encuestas versionadas
# =============================================================================

class EncuestaPlantilla(models.Model):
    TIPO_ESTUDIANTE = "ESTUDIANTE"
    TIPO_AUTOEVALUACION = "AUTOEVALUACION"
    TIPO_DIRECTOR = "DIRECTOR"
    TIPOS = [
        (TIPO_ESTUDIANTE, "Encuesta al estudiante"),
        (TIPO_AUTOEVALUACION, "Autoevaluacion docente"),
        (TIPO_DIRECTOR, "Encuesta al director de carrera"),
    ]

    tipo = models.CharField(max_length=20, choices=TIPOS)
    version = models.CharField(max_length=10, default="1.0")
    titulo = models.CharField(max_length=200)
    activa = models.BooleanField(
        default=True,
        help_text="Solo una plantilla activa por tipo debe usarse para generar nuevos tokens.",
    )

    class Meta:
        unique_together = ("tipo", "version")
        ordering = ["tipo", "-version"]

    def __str__(self):
        return f"{self.get_tipo_display()} v{self.version}"


class Pregunta(models.Model):
    ESCALA_LIKERT = "likert"
    ESCALA_ABIERTA = "abierta"
    ESCALAS = [(ESCALA_LIKERT, "Escala Likert 1-5"), (ESCALA_ABIERTA, "Respuesta abierta")]

    plantilla = models.ForeignKey(EncuestaPlantilla, on_delete=models.CASCADE, related_name="preguntas")
    orden = models.PositiveSmallIntegerField(default=1)
    texto = models.CharField(max_length=400)
    escala = models.CharField(max_length=10, choices=ESCALAS, default=ESCALA_LIKERT)
    requerida = models.BooleanField(default=True)

    class Meta:
        ordering = ["plantilla", "orden"]

    def __str__(self):
        return f"[{self.plantilla}] {self.texto[:50]}"


# =============================================================================
# Tokens (QR de uso unico) y Respuestas
# =============================================================================

def generar_token():
    return secrets.token_hex(16)


class Token(models.Model):
    ESTADO_NO_USADO = "no_usado"
    ESTADO_USADO = "usado"
    ESTADO_EXPIRADO = "expirado"
    ESTADOS = [
        (ESTADO_NO_USADO, "No usado"),
        (ESTADO_USADO, "Usado"),
        (ESTADO_EXPIRADO, "Expirado"),
    ]

    token = models.CharField(max_length=32, unique=True, default=generar_token, editable=False)
    tipo = models.CharField(max_length=20, choices=EncuestaPlantilla.TIPOS)
    grupo = models.ForeignKey(Grupo, on_delete=models.CASCADE, related_name="tokens")
    plantilla = models.ForeignKey(EncuestaPlantilla, on_delete=models.PROTECT, related_name="tokens")

    # Para tokens tipo ESTUDIANTE, un numero secuencial solo para diferenciar
    # el archivo PNG (ej. "Estudiante 07"); NO identifica a ninguna persona real.
    etiqueta = models.CharField(max_length=50, blank=True)

    estado = models.CharField(max_length=10, choices=ESTADOS, default=ESTADO_NO_USADO)
    creado_en = models.DateTimeField(auto_now_add=True)
    usado_en = models.DateTimeField(null=True, blank=True)

    class Meta:
        indexes = [models.Index(fields=["token"]), models.Index(fields=["grupo", "tipo"])]

    def __str__(self):
        return f"{self.tipo} - {self.token[:8]}... - {self.get_estado_display()}"


class Respuesta(models.Model):
    token = models.OneToOneField(Token, on_delete=models.CASCADE, related_name="respuesta")
    plantilla = models.ForeignKey(EncuestaPlantilla, on_delete=models.PROTECT)
    # {"12": 4, "13": 5, ..., "18": "comentario libre"} - sin datos identificables
    respuestas = models.JSONField()
    enviado_en = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"Respuesta {self.plantilla} - {self.enviado_en:%Y-%m-%d %H:%M}"
