"""
OTP Models — مدل ذخیره‌سازی کد یکبار مصرف.
"""
import hashlib
import secrets
import uuid

from django.db import models
from django.utils import timezone


class OTPCode(models.Model):
    """
    کد OTP یکبار مصرف.
    
    کد به صورت hash شده ذخیره می‌شود — plaintext ذخیره نمی‌شود.
    """

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    phone = models.CharField(max_length=20, db_index=True)
    code_hash = models.CharField(max_length=128)
    expires_at = models.DateTimeField()
    attempts = models.IntegerField(default=0)
    max_attempts = models.IntegerField(default=5)
    is_used = models.BooleanField(default=False)
    ip_address = models.GenericIPAddressField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'auth_otp_codes'
        verbose_name = 'OTP Code'
        verbose_name_plural = 'OTP Codes'
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['phone', 'created_at']),
        ]

    def __str__(self):
        return f"OTP for {self.phone} (expires {self.expires_at})"

    @property
    def is_expired(self):
        return timezone.now() >= self.expires_at

    @property
    def is_valid(self):
        return not self.is_used and not self.is_expired and self.attempts < self.max_attempts

    def verify(self, code: str) -> bool:
        """تأیید کد OTP."""
        if not self.is_valid:
            return False

        self.attempts += 1
        code_hash = self._hash_code(code)

        if code_hash == self.code_hash:
            self.is_used = True
            self.save(update_fields=['is_used', 'attempts'])
            return True

        self.save(update_fields=['attempts'])
        return False

    @staticmethod
    def _hash_code(code: str) -> str:
        """Hash کردن کد OTP."""
        return hashlib.sha256(code.encode()).hexdigest()

    @classmethod
    def generate(cls, phone: str, length: int = 5, expire_seconds: int = 120,
                 max_attempts: int = 5, ip_address: str | None = None) -> tuple['OTPCode', str]:
        """
        تولید و ذخیره OTP جدید.
        
        Returns:
            tuple: (OTPCode instance, plaintext code)
        """
        # Invalidate OTPs قبلی
        cls.objects.filter(
            phone=phone,
            is_used=False,
            expires_at__gt=timezone.now(),
        ).update(is_used=True)

        # Generate random code
        code = ''.join([str(secrets.randbelow(10)) for _ in range(length)])

        otp = cls.objects.create(
            phone=phone,
            code_hash=cls._hash_code(code),
            expires_at=timezone.now() + timezone.timedelta(seconds=expire_seconds),
            max_attempts=max_attempts,
            ip_address=ip_address,
        )

        return otp, code
