#![allow(clippy::too_many_lines)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::question_mark)]
#![allow(clippy::cast_precision_loss)]
use crate::adaptive_rate::host_state::HostState;
use crate::adaptive_rate::{
    DEFAULT_BACKOFF, ERROR_RATE_THRESHOLD, LATENCY_SPIKE_THRESHOLD, LATENCY_WINDOW_SIZE,
    MAX_BACKOFF, MIN_REQUESTS_FOR_ERROR_RATE, MIN_SAMPLES_FOR_SPIKE,
};
use parking_lot::{Mutex, RwLock};
use rustc_hash::FxHashMap;
use std::time::{Duration, Instant};
use guise_pacing::capped_pow2_backoff;

/// Status information about the current backoff state for a host.
pub struct BackoffStatus {
    /// Whether the host is currently being throttled.
    pub is_throttled: bool,
    /// Current backoff exponent (0 = no backoff).
    pub backoff_exponent: u32,
    /// Current backoff duration.
    pub current_backoff: Duration,
    /// Time until next request can be made (None if ready now).
    pub next_ready_in: Option<Duration>,
    /// Count of recent rate limit responses (429).
    pub recent_rate_limits: usize,
    /// Count of recent error responses (403, 503).
    pub recent_errors: usize,
    /// Total recent requests tracked.
    pub total_recent_requests: usize,
    /// EWMA latency in milliseconds (None if no data).
    pub ewma_latency_ms: Option<u64>,
}

/// Adapter that wraps `AdaptiveRateLimiter` to implement the sync `RateLimiter` trait.
///
/// This adapter provides synchronous access to the rate limiting functionality
/// by storing state in thread-safe data structures without async locks.
#[derive(Debug)]
pub struct SyncRateLimiter {
    hosts: RwLock<FxHashMap<String, Mutex<HostState>>>,
}

impl Default for SyncRateLimiter {
    fn default() -> Self {
        Self {
            hosts: RwLock::new(FxHashMap::default()),
        }
    }
}

impl SyncRateLimiter {
    /// Creates a new synchronous rate limiter.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Clears all tracked host state.
    pub fn clear_all(&self) {
        let mut guard = self.hosts.write();
        guard.clear();
    }
}

impl secir::plugin::RateLimiter for SyncRateLimiter {
    fn name(&self) -> &'static str {
        "sync-adaptive"
    }

    fn should_throttle(&self, host: &str) -> Option<std::time::Duration> {
        let guard = self.hosts.read();
        let Some(entry) = guard.get(host) else {
            return None;
        };
        let state = entry.lock();
        if state.is_throttled {
            let now = Instant::now();
            if now < state.next_ready_at {
                return Some(state.next_ready_at - now);
            }
        }
        None
    }

    fn record_response(&self, host: &str, status: u16, elapsed: Duration) {
        // Ensure the entry exists
        {
            let mut guard = self.hosts.write();
            guard
                .entry(host.to_string())
                .or_insert_with(|| Mutex::new(HostState::default()));
        }

        // Now get and lock the entry
        let guard = self.hosts.read();
        let Some(entry) = guard.get(host) else {
            return;
        };
        let mut state = entry.lock();

        // Update latency tracking
        if state.recent_latencies.len() >= LATENCY_WINDOW_SIZE {
            state.recent_latencies.remove(0);
        }
        state.recent_latencies.push(elapsed);

        // Update EWMA
        state.ewma_latency = Some(match state.ewma_latency {
            Some(current) => current.mul_f64(0.8) + elapsed.mul_f64(0.2),
            None => elapsed,
        });

        // Update request counters
        state.total_requests += 1;

        match status {
            429 => {
                let previous_exponent = state.backoff_exponent;
                state.backoff_exponent = state.backoff_exponent.saturating_add(1).min(10);

                let base = state
                    .ewma_latency
                    .map_or(DEFAULT_BACKOFF, |latency| latency.max(DEFAULT_BACKOFF));
                let penalty = capped_pow2_backoff(base, previous_exponent, MAX_BACKOFF);

                let ready_at = Instant::now() + penalty;
                if ready_at > state.next_ready_at {
                    state.next_ready_at = ready_at;
                }

                state.rate_limit_count += 1;
                state.is_throttled = true;
            }
            403 | 503 => {
                state.error_count += 1;

                if state.total_requests >= MIN_REQUESTS_FOR_ERROR_RATE {
                    let error_rate = state.error_count as f64 / state.total_requests as f64;
                    if error_rate >= ERROR_RATE_THRESHOLD {
                        let previous_exponent = state.backoff_exponent;
                        state.backoff_exponent = state.backoff_exponent.saturating_add(1).min(10);

                        let penalty =
                            capped_pow2_backoff(DEFAULT_BACKOFF, previous_exponent, MAX_BACKOFF);

                        let ready_at = Instant::now() + penalty;
                        if ready_at > state.next_ready_at {
                            state.next_ready_at = ready_at;
                        }

                        state.is_throttled = true;
                    } else if state.backoff_exponent > 0 {
                        state.backoff_exponent -= 1;
                        if state.backoff_exponent == 0 {
                            state.is_throttled = false;
                        }
                    }
                }
            }
            200..=299 => {
                state.is_throttled = false;
                state.last_success_at = Some(Instant::now());
                if state.backoff_exponent > 0 {
                    state.backoff_exponent -= 1;
                }
                if state.backoff_exponent == 0 && state.rate_limit_count > 0 {
                    state.rate_limit_count = 0;
                    state.error_count = 0;
                    state.total_requests = 0;
                }
            }
            _ => {
                if state.backoff_exponent > 0 {
                    state.backoff_exponent -= 1;
                    if state.backoff_exponent == 0 {
                        state.is_throttled = false;
                    }
                }
            }
        }

        // Check for latency spike
        if !state.is_throttled && state.recent_latencies.len() >= MIN_SAMPLES_FOR_SPIKE {
            let recent_avg: Duration = state.recent_latencies[..state.recent_latencies.len() - 1]
                .iter()
                .copied()
                .sum::<Duration>()
                / (state.recent_latencies.len() - 1) as u32;

            let most_recent = state.recent_latencies.last().copied().unwrap_or_default();

            let spike_detected = if recent_avg.as_secs_f64() > 0.0 {
                let ratio = most_recent.as_secs_f64() / recent_avg.as_secs_f64();
                ratio >= LATENCY_SPIKE_THRESHOLD
            } else {
                most_recent > Duration::from_secs(1)
            };

            if spike_detected {
                let previous_exponent = state.backoff_exponent;
                state.backoff_exponent = state.backoff_exponent.saturating_add(1).min(10);

                let penalty = capped_pow2_backoff(DEFAULT_BACKOFF, previous_exponent, MAX_BACKOFF);

                let ready_at = Instant::now() + penalty;
                if ready_at > state.next_ready_at {
                    state.next_ready_at = ready_at;
                }

                state.is_throttled = true;
            }
        }
    }
}
