from datetime import timedelta

from django.conf import settings
from django.db.models import Q
from django.utils import timezone

from .models import LoginAttempt


def client_ip(request):
    # Intentionally trusts REMOTE_ADDR only. Reverse proxies should normalize it at the web server layer.
    return request.META.get("REMOTE_ADDR") or "0.0.0.0"


def normalize_username(value):
    return (value or "").strip().lower()[:150]


def _bucket(username, ip):
    return LoginAttempt.objects.filter(username=username, ip_address=ip).first()


def current_attempt(request, username):
    username = normalize_username(username)
    if not username:
        return None
    return _bucket(username, client_ip(request))


def is_blocked(request, username):
    username = normalize_username(username)
    if not username:
        return False
    now = timezone.now()
    ip = client_ip(request)
    return LoginAttempt.objects.filter(
        Q(username=username, ip_address=ip) | Q(username="*", ip_address=ip),
        blocked_until__gt=now,
    ).exists()


def _record_bucket(*, username, ip, threshold, now):
    attempt, _ = LoginAttempt.objects.get_or_create(username=username, ip_address=ip)
    if attempt.blocked_until and attempt.blocked_until <= now:
        attempt.failure_count = 0
        attempt.first_failed_at = None
        attempt.blocked_until = None
    if not attempt.first_failed_at:
        attempt.first_failed_at = now
    attempt.failure_count += 1
    if attempt.failure_count >= threshold:
        attempt.blocked_until = now + timedelta(minutes=settings.LOGIN_LOCKOUT_MINUTES)
    attempt.save(update_fields=["failure_count", "first_failed_at", "blocked_until", "updated_at"])
    return attempt


def record_failure(request, username):
    username = normalize_username(username)
    if not username:
        return None
    now = timezone.now()
    ip = client_ip(request)
    exact = _record_bucket(username=username, ip=ip, threshold=settings.LOGIN_MAX_FAILED_ATTEMPTS, now=now)
    _record_bucket(username="*", ip=ip, threshold=settings.LOGIN_IP_MAX_FAILED_ATTEMPTS, now=now)
    return exact


def clear_failures(request, username):
    username = normalize_username(username)
    if username:
        # A successful login clears only the user-specific bucket. The IP-wide bucket
        # remains a short-lived signal against password-spraying across many users.
        LoginAttempt.objects.filter(username=username, ip_address=client_ip(request)).delete()
