import logging
import secrets
import json
from datetime import timedelta
from urllib.request import Request, urlopen

from django.conf import settings
from django.contrib.auth.hashers import check_password, make_password
from django.core.cache import cache
from django.core.exceptions import ImproperlyConfigured
from django.utils import timezone

logger = logging.getLogger(__name__)
SESSION_KEY = 'otp_challenge'


def issue_otp(request, phone, purpose, profile=None):
    previous = request.session.get(SESSION_KEY, {})
    now = timezone.now()
    if previous.get('sent_at'):
        sent_at = timezone.datetime.fromisoformat(previous['sent_at'])
        if now < sent_at + timedelta(seconds=settings.OTP_RESEND_SECONDS):
            return False
    throttle_key = f'otp-send:{phone}'
    if not cache.add(throttle_key, '1', timeout=settings.OTP_RESEND_SECONDS):
        return False
    code = f'{secrets.randbelow(1_000_000):06d}'
    challenge = {
        'phone': phone, 'purpose': purpose, 'profile': profile or {},
        'code_hash': make_password(code), 'sent_at': now.isoformat(),
        'expires_at': (now + timedelta(seconds=settings.OTP_TTL_SECONDS)).isoformat(), 'attempts': 0,
    }
    try:
        _send_code(phone, code)
    except Exception:
        cache.delete(throttle_key)
        raise
    request.session[SESSION_KEY] = challenge
    request.session.modified = True
    return True


def verify_otp(request, code):
    challenge = request.session.get(SESSION_KEY)
    if not challenge:
        return None, 'درخواست کد یافت نشد. دوباره تلاش کنید.'
    if timezone.now() >= timezone.datetime.fromisoformat(challenge['expires_at']):
        request.session.pop(SESSION_KEY, None)
        return None, 'کد منقضی شده است.'
    challenge['attempts'] += 1
    request.session[SESSION_KEY] = challenge
    if challenge['attempts'] > settings.OTP_MAX_ATTEMPTS:
        request.session.pop(SESSION_KEY, None)
        return None, 'تعداد تلاش‌ها بیش از حد مجاز است.'
    if not check_password(code, challenge['code_hash']):
        return None, 'کد واردشده صحیح نیست.'
    request.session.pop(SESSION_KEY, None)
    return challenge, None


def _send_code(phone, code):
    if settings.OTP_BACKEND == 'console':
        if not settings.DEBUG:
            raise ImproperlyConfigured('OTP کنسولی در محیط عملیاتی مجاز نیست.')
        logger.warning('Development OTP for %s: %s', phone, code)
        return
    if settings.OTP_BACKEND == 'webhook':
        if not settings.OTP_WEBHOOK_URL:
            raise ImproperlyConfigured('OTP_WEBHOOK_URL تنظیم نشده است.')
        payload = json.dumps({'phone': phone, 'code': code, 'template': settings.OTP_TEMPLATE}).encode('utf-8')
        headers = {'Content-Type': 'application/json'}
        if settings.OTP_WEBHOOK_TOKEN:
            headers['Authorization'] = f'Bearer {settings.OTP_WEBHOOK_TOKEN}'
        with urlopen(Request(settings.OTP_WEBHOOK_URL, data=payload, headers=headers, method='POST'), timeout=10) as response:
            if not 200 <= response.status < 300:
                raise RuntimeError(f'OTP provider returned HTTP {response.status}')
        return
    raise ImproperlyConfigured('برای محیط عملیاتی، OTP_BACKEND را به سرویس پیامک معتبر متصل کنید.')
