//! Anomaly Detection Module
//!
//! Detects abnormal API key usage patterns

use crate::core::api_key::models::*;
use crate::core::api_key::service::AnomalyMetrics;
use chrono::{Duration, Timelike, Utc};
use std::collections::HashMap;
use tokio::sync::RwLock;

pub struct AnomalyDetector {
    config: AnomalyDetectionConfig,
    metrics_cache: RwLock<HashMap<String, Vec<RequestMetric>>>,
    lockout_cache: RwLock<HashMap<String, i32>>,
}

#[derive(Debug, Clone)]
struct RequestMetric {
    timestamp: chrono::DateTime<Utc>,
    ip: Option<String>,
    is_error: bool,
}

impl AnomalyDetector {
    pub fn new(config: AnomalyDetectionConfig) -> Self {
        Self {
            config,
            metrics_cache: RwLock::new(HashMap::new()),
            lockout_cache: RwLock::new(HashMap::new()),
        }
    }

    pub async fn record_request(&self, key_id: &str, ip: Option<String>, is_error: bool) {
        let metric = RequestMetric {
            timestamp: Utc::now(),
            ip,
            is_error,
        };

        let mut cache = self.metrics_cache.write().await;
        cache.entry(key_id.to_string()).or_default().push(metric);

        self.cleanup_old_metrics(&mut cache).await;
    }

    async fn cleanup_old_metrics(&self, cache: &mut HashMap<String, Vec<RequestMetric>>) {
        let cutoff = Utc::now() - Duration::hours(2);

        for metrics in cache.values_mut() {
            metrics.retain(|m| m.timestamp > cutoff);
        }
    }

    pub async fn check_anomaly(&self, key_id: &str) -> Option<AnomalyRecord> {
        let cache = self.metrics_cache.read().await;
        let metrics = cache.get(key_id)?;

        let now = Utc::now();
        let last_minute = now - Duration::minutes(1);
        let last_hour = now - Duration::hours(1);

        let recent = metrics
            .iter()
            .filter(|m| m.timestamp > last_hour)
            .collect::<Vec<_>>();

        let requests_per_minute =
            metrics.iter().filter(|m| m.timestamp > last_minute).count() as i32;
        let error_count = recent.iter().filter(|m| m.is_error).count() as i32;
        let error_rate = if recent.is_empty() {
            0.0
        } else {
            error_count as f64 / recent.len() as f64
        };

        let unique_ips = recent
            .iter()
            .filter_map(|m| m.ip.clone())
            .collect::<std::collections::HashSet<_>>()
            .len() as i32;

        let last_ip = metrics.last().and_then(|m| m.ip.clone());

        let metrics = AnomalyMetrics {
            requests_per_minute,
            error_count,
            error_rate,
            unique_ips,
            last_ip,
        };

        self.detect_anomaly(key_id, metrics).await
    }

    async fn detect_anomaly(&self, key_id: &str, metrics: AnomalyMetrics) -> Option<AnomalyRecord> {
        if metrics.requests_per_minute > self.config.requests_per_minute_threshold * 10 {
            let mut lockout = self.lockout_cache.write().await;
            *lockout.entry(key_id.to_string()).or_insert(0) += 1;

            if lockout[&key_id.to_string()] >= self.config.lockout_threshold {
                return Some(self.create_anomaly(
                    key_id,
                    AnomalyType::BruteForce,
                    AnomalySeverity::Critical,
                    &metrics,
                ));
            }
        }

        if metrics.requests_per_minute > self.config.requests_per_minute_threshold {
            return Some(self.create_anomaly(
                key_id,
                AnomalyType::HighRequestRate,
                AnomalySeverity::Medium,
                &metrics,
            ));
        }

        if metrics.error_rate > self.config.error_rate_threshold {
            return Some(self.create_anomaly(
                key_id,
                AnomalyType::HighErrorRate,
                AnomalySeverity::Medium,
                &metrics,
            ));
        }

        if metrics.unique_ips > self.config.unique_ips_per_hour_threshold {
            return Some(self.create_anomaly(
                key_id,
                AnomalyType::MultipleIps,
                AnomalySeverity::Low,
                &metrics,
            ));
        }

        let hour = Utc::now().hour();
        if hour < 6 && metrics.requests_per_minute > 10 {
            return Some(self.create_anomaly(
                key_id,
                AnomalyType::UnusualTime,
                AnomalySeverity::Low,
                &metrics,
            ));
        }

        None
    }

    fn create_anomaly(
        &self,
        key_id: &str,
        anomaly_type: AnomalyType,
        severity: AnomalySeverity,
        metrics: &AnomalyMetrics,
    ) -> AnomalyRecord {
        AnomalyRecord {
            id: 0,
            key_id: key_id.to_string(),
            anomaly_type,
            severity,
            ip_address: metrics.last_ip.clone(),
            request_count: Some(metrics.requests_per_minute),
            error_count: Some(metrics.error_count),
            error_rate: Some(metrics.error_rate),
            unique_ips: Some(metrics.unique_ips),
            details: None,
            resolved: false,
            resolved_at: None,
            resolved_by: None,
            created_at: Utc::now(),
        }
    }

    pub async fn should_lockout(&self, key_id: &str) -> bool {
        let lockout = self.lockout_cache.read().await;
        lockout.get(key_id).copied().unwrap_or(0) >= self.config.lockout_threshold
    }

    pub async fn reset_lockout(&self, key_id: &str) {
        let mut lockout = self.lockout_cache.write().await;
        lockout.remove(key_id);

        let mut cache = self.metrics_cache.write().await;
        cache.remove(key_id);
    }
}

impl Default for AnomalyDetector {
    fn default() -> Self {
        Self::new(AnomalyDetectionConfig::default())
    }
}
