"""
security/models.py
==================
Modelos de seguridad y auditoría.

AuditLog registra TODAS las acciones críticas del sistema:
  - Logins exitosos y fallidos
  - Generación y uso de tokens
  - Activación de preguntas
  - Respuestas sospechosas
  - Notas asignadas
  - Canjes aprobados/rechazados

Esto cumple con el requerimiento de trazabilidad de la plataforma.
"""

from django.db import models
from django.conf import settings
from shared.mixins import TimestampedModel


class AuditLog(TimestampedModel):
    """
    Log de auditoría inmutable.
    Una vez creado, NO se debe modificar ni eliminar.
    Los logs mayores a AUDIT_RETENTION_DAYS se archivan (no se borran).
    """

    class Accion(models.TextChoices):
        LOGIN_OK              = 'LOGIN_OK',              'Login exitoso'
        LOGIN_FALLIDO         = 'LOGIN_FALLIDO',         'Login fallido'
        LOGOUT                = 'LOGOUT',                'Logout'
        REGISTRO_DOCENTE      = 'REGISTRO_DOCENTE',      'Registro docente'
        REGISTRO_ALUMNO       = 'REGISTRO_ALUMNO',       'Registro alumno'
        DOCENTE_APROBADO      = 'DOCENTE_APROBADO',      'Docente aprobado'
        DOCENTE_RECHAZADO     = 'DOCENTE_RECHAZADO',     'Docente rechazado'
        SESION_ABIERTA        = 'SESION_ABIERTA',        'Sesión abierta'
        SESION_CERRADA        = 'SESION_CERRADA',        'Sesión cerrada'
        PREGUNTA_ACTIVADA     = 'PREGUNTA_ACTIVADA',     'Pregunta activada'
        RESPUESTA_ENVIADA     = 'RESPUESTA_ENVIADA',     'Respuesta enviada'
        RESPUESTA_SOSPECHOSA  = 'RESPUESTA_SOSPECHOSA',  'Respuesta sospechosa'
        CODIGO_GENERADO       = 'CODIGO_GENERADO',       'Código generado'
        TOKEN_GENERADO        = 'TOKEN_GENERADO',        'Token de registro generado'
        TOKEN_USADO           = 'TOKEN_USADO',           'Token de registro usado'
        CANJE_FICHA           = 'CANJE_FICHA',           'Canje de ficha'
        NOTA_ASIGNADA         = 'NOTA_ASIGNADA',         'Nota asignada'
        AJUSTE_FICHAS         = 'AJUSTE_FICHAS',         'Ajuste manual de fichas'
        IP_BLOQUEADA          = 'IP_BLOQUEADA',          'IP bloqueada'

    # Usuario que realizó la acción (null si es acción anónima, ej: login fallido)
    usuario  = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='audit_logs',
        verbose_name='Usuario'
    )
    accion   = models.CharField(max_length=25, choices=Accion.choices, db_index=True)
    ip       = models.GenericIPAddressField(null=True, blank=True, verbose_name='IP de origen')
    detalle  = models.TextField(blank=True, verbose_name='Detalle adicional')
    # Metadata HTTP (útil para análisis forense)
    user_agent = models.CharField(max_length=500, blank=True)
    endpoint   = models.CharField(max_length=200, blank=True)

    class Meta(TimestampedModel.Meta):
        verbose_name = 'Log de auditoría'
        verbose_name_plural = 'Logs de auditoría'
        db_table = 'security_auditlog'
        # Logs son inmutables — no se ordenan por default para no añadir overhead
        ordering = []
        indexes = [
            models.Index(fields=['accion', 'created_at']),
            models.Index(fields=['ip', 'accion']),
            models.Index(fields=['usuario', 'created_at']),
        ]

    def __str__(self):
        return f'{self.accion} — {self.usuario_id or "anon"} — {self.ip}'

    @classmethod
    def registrar(cls, accion: str, usuario=None, ip: str = None,
                  detalle: str = '', request=None):
        """
        Factory method para crear logs de forma consistente.
        Extrae IP y user-agent del request si se proporciona.
        """
        ip_final = ip
        ua       = ''
        endpoint = ''

        if request:
            ip_final = cls._get_client_ip(request)
            ua       = request.META.get('HTTP_USER_AGENT', '')[:500]
            endpoint = request.path[:200]

        cls.objects.create(
            usuario=usuario, accion=accion, ip=ip_final,
            detalle=detalle[:2000], user_agent=ua, endpoint=endpoint,
        )

    @staticmethod
    def _get_client_ip(request) -> str:
        """Obtiene la IP real del cliente considerando proxies."""
        forwarded = request.META.get('HTTP_X_FORWARDED_FOR')
        if forwarded:
            return forwarded.split(',')[0].strip()
        return request.META.get('REMOTE_ADDR', '')


class IPBlocklist(TimestampedModel):
    """
    IPs bloqueadas por exceso de intentos fallidos.
    Se crean automáticamente por el middleware RateLimit.
    """
    ip          = models.GenericIPAddressField(unique=True)
    motivo      = models.CharField(max_length=200)
    bloqueada_hasta = models.DateTimeField()
    intentos    = models.PositiveSmallIntegerField(default=0)

    class Meta(TimestampedModel.Meta):
        verbose_name = 'IP bloqueada'
        db_table = 'security_ipblocklist'

    def __str__(self):
        return f'{self.ip} bloqueada hasta {self.bloqueada_hasta}'
