"""
Threat detection against NginxAccessLog for each enabled NginxAlertProfile.
"""
import logging
from datetime import timedelta

from django.db.models import Count, Q
from django.utils import timezone

from .models import NginxAlertProfile, NginxAccessLog, ThreatAlert, IPGeoCache
from .notifications import send_alert_telegram

logger = logging.getLogger(__name__)


def detect_threats(profile_id: int):
    try:
        profile = NginxAlertProfile.objects.get(pk=profile_id, enabled=True)
    except NginxAlertProfile.DoesNotExist:
        return

    window = timedelta(seconds=profile.alert_window_seconds)
    now = timezone.now()
    window_start = now - window

    qs = (
        NginxAccessLog.objects
        .filter(timestamp__gte=window_start)
        .values('remote_addr')
        .annotate(
            total=Count('id'),
            errors=Count('id', filter=Q(status__gte=400)),
        )
    )

    for row in qs:
        ip, total, errors = row['remote_addr'], row['total'], row['errors']

        existing = ThreatAlert.objects.filter(
            profile=profile,
            remote_addr=ip,
            window_start__gte=window_start,
            dismissed=False,
        )

        if total >= profile.alert_max_requests:
            if not existing.filter(alert_type='rate_limit').exists():
                _create_alert(profile, 'rate_limit', ip, total, errors, window_start, now)

        if total >= profile.alert_min_requests:
            if (errors / total) >= profile.alert_max_error_rate:
                if not existing.filter(alert_type='error_rate').exists():
                    _create_alert(profile, 'error_rate', ip, total, errors, window_start, now)


def _create_alert(profile, alert_type, ip, total, errors, window_start, window_end):
    geo = IPGeoCache.objects.filter(ip=ip).first()
    country = geo.country if geo else ''
    city = geo.city if geo else ''

    if alert_type == 'rate_limit':
        detail = (
            f'{total} requests from {ip} in the last {profile.alert_window_seconds}s '
            f'(threshold: {profile.alert_max_requests})'
        )
    else:
        rate_pct = round(errors / total * 100)
        detail = (
            f'{errors}/{total} requests from {ip} failed '
            f'({rate_pct}% error rate, threshold: {int(profile.alert_max_error_rate * 100)}%)'
        )

    alert = ThreatAlert.objects.create(
        profile=profile,
        alert_type=alert_type,
        remote_addr=ip,
        window_start=window_start,
        window_end=window_end,
        request_count=total,
        error_count=errors,
        detail=detail,
        country=country,
        city=city,
    )
    logger.warning('nginxmon alert: %s', detail)
    send_alert_telegram(profile, alert)
