from datetime import timedelta

from django.conf import settings
from django.db import models
from django.utils import timezone


class MFASettings(models.Model):
    """The SUPER ADMIN sets ONE active MFA method here. Saving pushes it to the
    MCP server, so EVERY project (TA-ATS, DEMO, Demo3...) follows it."""

    class Method(models.TextChoices):
        NONE = "none", "None (no MFA)"
        TOTP = "totp", "Authenticator App"
        EMAIL = "email", "Email OTP"
        SMS = "sms", "Mobile OTP"

    method = models.CharField(max_length=10, choices=Method.choices, default=Method.EMAIL)

    class Meta:
        db_table = "mfa_settings"
        verbose_name = "MFA Settings"
        verbose_name_plural = "MFA Settings"

    def __str__(self):
        return f"MFA method: {self.method}"

    @classmethod
    def load(cls):
        obj, _ = cls.objects.get_or_create(pk=1)
        return obj

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        # push the chosen method to the central MCP policy (best-effort)
        try:
            from . import mcp_client
            mcp_client.set_mfa_policy(self.method)
        except Exception:
            pass


class LoginAttempt(models.Model):
    MAX_FAILED_ATTEMPTS = 5
    LOCKOUT_MINUTES = 30

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="login_attempt",
    )
    failed_attempts = models.PositiveIntegerField(default=0)
    is_locked = models.BooleanField(default=False)
    locked_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        db_table = "login_attempts"

    def _is_exempt(self) -> bool:
        """Admins are exempt from the lockout policy: their failures are never
        counted and they can never be locked out."""
        return (getattr(self.user, "role", "") or "").upper() == "ADMIN" or self.user.is_superuser

    def is_currently_locked(self) -> bool:
        if self._is_exempt():
            # Self-heal any lock recorded before the account became admin.
            if self.is_locked or self.failed_attempts:
                self.reset()
            return False
        if self.is_locked and self.locked_at:
            if timezone.now() > self.locked_at + timedelta(minutes=self.LOCKOUT_MINUTES):
                self.reset()
                return False
            return True
        return False

    def register_failure(self):
        if self._is_exempt():
            return
        self.failed_attempts += 1
        if self.failed_attempts >= self.MAX_FAILED_ATTEMPTS:
            self.is_locked = True
            self.locked_at = timezone.now()
        self.save(update_fields=["failed_attempts", "is_locked", "locked_at"])

    def reset(self):
        self.failed_attempts = 0
        self.is_locked = False
        self.locked_at = None
        self.save(update_fields=["failed_attempts", "is_locked", "locked_at"])


class PasswordPolicy(models.Model):
    """Global password expiry policy — singleton (pk=1 always)."""

    expiry_days = models.PositiveIntegerField(default=90)

    class Meta:
        db_table = "password_policy"
        verbose_name = "Password Policy"

    def __str__(self):
        return f"Password expires every {self.expiry_days} days"

    @classmethod
    def load(cls):
        obj, _ = cls.objects.get_or_create(pk=1, defaults={"expiry_days": 90})
        return obj

    def get_expiry_timedelta(self):
        from datetime import timedelta
        return timedelta(days=self.expiry_days)


class OTPRecord(models.Model):
    OTP_TTL_MINUTES = 5

    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="otp_record",
    )
    otp_code = models.CharField(max_length=6)
    created_at = models.DateTimeField(default=timezone.now)
    retry_count = models.PositiveIntegerField(default=0)
    resend_count = models.PositiveIntegerField(default=0)

    class Meta:
        db_table = "otp_records"

    def is_expired(self) -> bool:
        return timezone.now() > self.created_at + timedelta(minutes=self.OTP_TTL_MINUTES)
