"""
OTP Service — سرویس مرکزی مدیریت OTP.

شامل تولید، ارسال، تأیید و rate limiting.
"""
import logging
import re
from datetime import timedelta

from django.conf import settings
from django.core.cache import cache
from django.utils import timezone

from .otp_models import OTPCode

logger = logging.getLogger('apps')


class OTPService:
    """سرویس OTP با rate limiting و SMS integration."""

    # Default settings
    DEFAULT_OTP_LENGTH = 5
    DEFAULT_EXPIRE_SECONDS = 120
    DEFAULT_MAX_ATTEMPTS = 5
    DEFAULT_RETRY_DELAY = 60
    DEFAULT_MAX_PER_PHONE_PER_HOUR = 5
    DEFAULT_MAX_PER_IP_PER_HOUR = 10

    @classmethod
    def send_otp(cls, phone: str, ip_address: str | None = None,
                 tenant_settings: dict | None = None) -> dict:
        """
        ارسال OTP به شماره موبایل.
        
        Returns:
            dict: {success, message, retry_after}
        """
        # Normalize phone
        phone = cls.normalize_phone(phone)
        if not phone:
            return {'success': False, 'message': 'شماره موبایل نامعتبر است'}

        settings_dict = tenant_settings or {}
        otp_length = settings_dict.get('otp_length', cls.DEFAULT_OTP_LENGTH)
        expire_seconds = settings_dict.get('otp_expire_seconds', cls.DEFAULT_EXPIRE_SECONDS)
        max_attempts = settings_dict.get('otp_max_attempts', cls.DEFAULT_MAX_ATTEMPTS)
        retry_delay = settings_dict.get('otp_retry_delay_seconds', cls.DEFAULT_RETRY_DELAY)

        # Rate limiting
        rate_check = cls._check_rate_limit(phone, ip_address)
        if not rate_check['allowed']:
            return {
                'success': False,
                'message': rate_check['message'],
                'retry_after': rate_check.get('retry_after', 0),
            }

        # Check retry delay
        last_otp = OTPCode.objects.filter(
            phone=phone,
        ).order_by('-created_at').first()

        if last_otp:
            seconds_since = (timezone.now() - last_otp.created_at).total_seconds()
            if seconds_since < retry_delay:
                remaining = int(retry_delay - seconds_since)
                return {
                    'success': False,
                    'message': f'لطفاً {remaining} ثانیه صبر کنید',
                    'retry_after': remaining,
                }

        # Generate OTP
        otp, code = OTPCode.generate(
            phone=phone,
            length=otp_length,
            expire_seconds=expire_seconds,
            max_attempts=max_attempts,
            ip_address=ip_address,
        )

        # Send SMS
        sms_sent = cls._send_sms(phone, code)

        if not sms_sent:
            logger.error(f"Failed to send OTP SMS to {phone}")
            return {
                'success': False,
                'message': 'خطا در ارسال پیامک. لطفاً مجدداً تلاش کنید',
            }

        # Increment rate limit counters
        cls._increment_rate_limit(phone, ip_address)

        logger.info(f"OTP sent to {phone}")
        return {
            'success': True,
            'message': 'کد تأیید ارسال شد',
            'retry_after': retry_delay,
            'expires_in': expire_seconds,
        }

    @classmethod
    def verify_otp(cls, phone: str, code: str) -> dict:
        """
        تأیید کد OTP.
        
        Returns:
            dict: {success, message, user (if exists)}
        """
        phone = cls.normalize_phone(phone)
        if not phone:
            return {'success': False, 'message': 'شماره موبایل نامعتبر است'}

        # Find latest valid OTP
        otp = OTPCode.objects.filter(
            phone=phone,
            is_used=False,
            expires_at__gt=timezone.now(),
        ).order_by('-created_at').first()

        if not otp:
            return {'success': False, 'message': 'کد تأیید منقضی شده یا وجود ندارد'}

        if not otp.is_valid:
            return {'success': False, 'message': 'کد تأیید منقضی شده یا تعداد تلاش به حد مجاز رسیده'}

        if otp.verify(code):
            return {'success': True, 'message': 'کد تأیید صحیح است'}

        remaining = otp.max_attempts - otp.attempts
        return {
            'success': False,
            'message': f'کد تأیید اشتباه است. {remaining} تلاش باقیمانده',
        }

    @classmethod
    def normalize_phone(cls, phone: str) -> str | None:
        """
        نرمال‌سازی شماره موبایل به فرمت E.164.
        
        ورودی‌های مجاز:
        - 09123456789
        - 9123456789
        - +989123456789
        - ۰۹۱۲۳۴۵۶۷۸۹
        """
        if not phone:
            return None

        # Convert Persian/Arabic digits to Latin
        persian_digits = '۰۱۲۳۴۵۶۷۸۹'
        arabic_digits = '٠١٢٣٤٥٦٧٨٩'
        for i in range(10):
            phone = phone.replace(persian_digits[i], str(i))
            phone = phone.replace(arabic_digits[i], str(i))

        # Remove spaces, dashes, parentheses
        phone = re.sub(r'[\s\-\(\)]', '', phone)

        # Normalize
        if phone.startswith('+98'):
            phone = phone[1:]  # Remove +
        elif phone.startswith('09'):
            phone = '98' + phone[1:]
        elif phone.startswith('9') and len(phone) == 10:
            phone = '98' + phone
        elif phone.startswith('0098'):
            phone = phone[2:]

        # Validate
        if re.match(r'^98\d{10}$', phone):
            return '+' + phone

        return None

    @classmethod
    def _check_rate_limit(cls, phone: str, ip_address: str | None = None) -> dict:
        """بررسی rate limit."""
        # Per phone per hour
        phone_key = f'otp_rate:phone:{phone}'
        phone_count = cache.get(phone_key, 0)
        if phone_count >= cls.DEFAULT_MAX_PER_PHONE_PER_HOUR:
            return {
                'allowed': False,
                'message': 'تعداد درخواست‌های ارسال کد تأیید به حد مجاز رسیده. لطفاً یک ساعت دیگر تلاش کنید',
                'retry_after': 3600,
            }

        # Per IP per hour
        if ip_address:
            ip_key = f'otp_rate:ip:{ip_address}'
            ip_count = cache.get(ip_key, 0)
            if ip_count >= cls.DEFAULT_MAX_PER_IP_PER_HOUR:
                return {
                    'allowed': False,
                    'message': 'تعداد درخواست‌ها از این آدرس IP به حد مجاز رسیده',
                    'retry_after': 3600,
                }

        return {'allowed': True}

    @classmethod
    def _increment_rate_limit(cls, phone: str, ip_address: str | None = None):
        """افزایش شمارنده rate limit."""
        phone_key = f'otp_rate:phone:{phone}'
        try:
            cache.incr(phone_key)
        except ValueError:
            cache.set(phone_key, 1, timeout=3600)

        if ip_address:
            ip_key = f'otp_rate:ip:{ip_address}'
            try:
                cache.incr(ip_key)
            except ValueError:
                cache.set(ip_key, 1, timeout=3600)

    @classmethod
    def _send_sms(cls, phone: str, code: str) -> bool:
        """ارسال SMS با استفاده از notification SMS provider."""
        try:
            from apps.services.notification.providers.sms import SMSProvider
            provider = SMSProvider()
            message = f'کد تأیید شما: {code}'
            response = provider.send(recipient=phone, content=message)
            return response.success
        except Exception as e:
            logger.error(f"SMS send error: {e}", exc_info=True)
            # In development mode, log the code
            if getattr(settings, 'DEBUG', False):
                logger.info(f"[DEV] OTP code for {phone}: {code}")
                return True
            return False
