"""سرویس OTP — همان زنجیره torbatkar-back/User/utils.send_phone_otp_shared."""
from __future__ import annotations

import random
import secrets

from app.core.cache import cache
from app.core.config import settings
from app.services.payamak_client import PayamakClient, SmsDeliveryError, _normalize_phone_for_sms
from app.utils.phone import normalize_mobile, normalize_otp_digits

_memory_otp: dict[str, str] = {}


def _otp_key(mobile: str) -> str:
    return f"otp:{mobile}"


class OtpService:
    def __init__(self) -> None:
        self.sms = PayamakClient()

    def _generate_code(self) -> str:
        if settings.environment == "test":
            return "1234"
        return str(random.randint(1111, 9999))

    async def _get_stored_code(self, mobile: str) -> str | None:
        key = _otp_key(mobile)
        stored = await cache.get(key)
        if stored is None and cache.client is None:
            stored = _memory_otp.get(mobile)
        return stored

    async def request_otp(self, mobile: str, *, force: bool = False) -> tuple[int, bool]:
        """
        Returns:
            (expires_in, skipped) — skipped=True یعنی کد قبلی هنوز معتبر است (torbatkar).
        """
        normalized = normalize_mobile(mobile)
        phone_for_sms = _normalize_phone_for_sms(normalized)
        if not phone_for_sms:
            raise SmsDeliveryError("شماره موبایل نامعتبر است")

        key = _otp_key(normalized)
        if not force:
            stored = await self._get_stored_code(normalized)
            if stored:
                remaining = await cache.ttl(key)
                expires_in = remaining if remaining else settings.otp_expire_seconds
                return expires_in, True

        code = self._generate_code()
        ttl = settings.otp_expire_seconds

        await cache.set(key, code, ttl=ttl)
        if cache.client is None:
            _memory_otp[normalized] = code

        await self.sms.send_otp(phone_for_sms, code)
        return ttl, False

    async def verify_otp(self, mobile: str, code: str) -> bool:
        normalized_code = normalize_otp_digits(code)
        if not normalized_code:
            return False

        normalized = normalize_mobile(mobile)
        key = _otp_key(normalized)
        stored = await self._get_stored_code(normalized)

        if stored is None or not secrets.compare_digest(stored, normalized_code):
            return False

        await cache.delete(key)
        _memory_otp.pop(normalized, None)
        return True
