from __future__ import annotations

import hashlib
import time
from typing import Iterable

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.cache import cache

User = get_user_model()


def get_client_ip(request) -> str:
    forwarded = request.META.get("HTTP_X_FORWARDED_FOR")
    if forwarded:
        return forwarded.split(",", 1)[0].strip()
    return request.META.get("REMOTE_ADDR", "unknown")


def _rate_limit_key(scope: str, identifier: str, ip_address: str) -> str:
    source = f"{scope}:{identifier}:{ip_address}".encode("utf-8")
    digest = hashlib.sha256(source).hexdigest()
    return f"auth-rate:{scope}:{digest}"


def is_rate_limited(
    *,
    scope: str,
    identifier: str,
    ip_address: str,
    max_attempts: int,
    window_seconds: int,
) -> bool:
    key = _rate_limit_key(scope, identifier, ip_address)
    payload = cache.get(key)

    if not payload:
        return False

    first_attempt = payload.get("first_attempt", 0)
    elapsed = time.time() - first_attempt

    if elapsed > window_seconds:
        cache.delete(key)
        return False

    return payload.get("count", 0) >= max_attempts


def get_failed_attempt_count(
    *,
    scope: str,
    identifier: str,
    ip_address: str,
    window_seconds: int,
) -> int:
    key = _rate_limit_key(scope, identifier, ip_address)
    payload = cache.get(key)
    if not payload:
        return 0

    first_attempt = payload.get("first_attempt", 0)
    if time.time() - first_attempt > window_seconds:
        cache.delete(key)
        return 0

    return int(payload.get("count", 0) or 0)


def register_failed_attempt(
    *,
    scope: str,
    identifier: str,
    ip_address: str,
    window_seconds: int,
) -> int:
    key = _rate_limit_key(scope, identifier, ip_address)
    payload = cache.get(key)
    now = time.time()

    if not payload or now - payload.get("first_attempt", 0) > window_seconds:
        payload = {"count": 0, "first_attempt": now}

    payload["count"] += 1
    cache.set(key, payload, timeout=window_seconds)
    return int(payload["count"])


def clear_failed_attempts(*, scope: str, identifier: str, ip_address: str) -> None:
    key = _rate_limit_key(scope, identifier, ip_address)
    cache.delete(key)


def has_any_role(user: User, roles: Iterable[str]) -> bool:
    if not user.is_authenticated:
        return False
    if user.is_superuser:
        return True
    return user.role in set(roles)


def is_super_admin(user: User) -> bool:
    return bool(
        user.is_authenticated
        and (
            user.is_superuser
            or user.role == user.Role.SUPERUSER
        )
    )


def is_staff_or_admin(user: User) -> bool:
    return bool(user.is_authenticated and (user.is_staff or is_super_admin(user)))


def should_enable_otp() -> bool:
    return bool(getattr(settings, "AUTH_OTP_ENABLED", False))
