#![allow(clippy::cast_precision_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::unused_self)]
use rustc_hash::FxHashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::Mutex as AsyncMutex;
use tokio::sync::RwLock;

use crate::adaptive_rate::backoff::BackoffStatus;
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 guise_pacing::capped_pow2_backoff;

/// Adaptive rate limiter that detects rate-limiting signals and backs off per host.
///
/// The limiter tracks response patterns for each host and automatically applies
/// exponential backoff when rate limiting is detected. It monitors:
/// - HTTP 429 (Too Many Requests) responses
/// - Sudden increases in 403/503 error rates
/// - Response time spikes
///
/// When conditions improve (successful responses), the backoff gradually decreases.
///
/// # Example
///
/// ```
/// use scanclient::adaptive_rate::AdaptiveRateLimiter;
/// use std::time::Duration;
///
/// # async fn example() {
/// let limiter = AdaptiveRateLimiter::new();
///
/// // Check if we should throttle requests to a host
/// if !limiter.should_throttle("example.com").await {
///     // Make the request...
///     // Then record the response
///     limiter.record_response("example.com", 200, Duration::from_millis(100)).await;
/// }
/// # }
/// ```
#[derive(Debug)]
pub struct AdaptiveRateLimiter {
    hosts: RwLock<FxHashMap<String, Arc<AsyncMutex<HostState>>>>,
}

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

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

    /// Checks if requests to the given host should be throttled.
    ///
    /// Returns `true` if the host is currently under backoff due to
    /// detected rate limiting, `false` otherwise.
    ///
    /// # Arguments
    ///
    /// * `host` - The host to check (e.g., "example.com")
    pub async fn should_throttle(&self, host: &str) -> bool {
        let state = self.get_state(host).await;
        let state = state.lock().await;

        if state.is_throttled {
            let now = Instant::now();
            if now < state.next_ready_at {
                return true;
            }
        }
        false
    }

    /// Returns the duration until the host is ready for the next request.
    ///
    /// Returns `None` if the host is ready immediately.
    ///
    /// # Arguments
    ///
    /// * `host` - The host to check
    pub async fn throttle_duration(&self, host: &str) -> Option<Duration> {
        let state = self.get_state(host).await;
        let state = state.lock().await;

        let now = Instant::now();
        state.next_ready_at.checked_duration_since(now)
    }

    /// Records a response from a host to update rate limiting state.
    ///
    /// This method should be called after each request to update the
    /// internal tracking of response patterns.
    ///
    /// # Arguments
    ///
    /// * `host` - The host that was queried
    /// * `status` - The HTTP status code of the response
    /// * `elapsed` - The time taken for the response
    pub async fn record_response(&self, host: &str, status: u16, elapsed: Duration) {
        let state = self.get_state(host).await;
        let mut state = state.lock().await;

        // Update latency tracking
        self.update_latency(&mut state, elapsed);

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

        match status {
            429 => {
                // Explicit rate limit - apply exponential backoff
                self.apply_backoff(&mut state, true);
                state.rate_limit_count += 1;
                state.is_throttled = true;
            }
            403 | 503 => {
                // Potential blocking - track errors
                state.error_count += 1;

                // Check if error rate is high enough to trigger backoff
                if self.should_backoff_due_to_errors(&state) {
                    self.apply_backoff(&mut state, false);
                    state.is_throttled = true;
                } else {
                    // Partial recovery on non-error responses
                    self.maybe_reduce_backoff(&mut state);
                }
            }
            200..=299 => {
                // Success - clear throttling and reduce backoff
                state.is_throttled = false;
                state.last_success_at = Some(Instant::now());
                self.maybe_reduce_backoff(&mut state);

                // Reset counters on sustained success (when backoff is cleared
                // and we've had enough successful requests to indicate recovery)
                if state.backoff_exponent == 0 && state.rate_limit_count > 0 {
                    state.rate_limit_count = 0;
                    state.error_count = 0;
                    state.total_requests = 0;
                }
            }
            _ => {
                // Other responses - gradual backoff reduction
                self.maybe_reduce_backoff(&mut state);
            }
        }

        // Check for latency spikes as additional signal
        if self.detect_latency_spike(&state) && !state.is_throttled {
            self.apply_backoff(&mut state, false);
            state.is_throttled = true;
        }
    }

    /// Returns the current backoff status for a host.
    ///
    /// This is useful for monitoring and debugging rate limiting behavior.
    ///
    /// # Arguments
    ///
    /// * `host` - The host to check
    pub async fn get_backoff_status(&self, host: &str) -> BackoffStatus {
        let state = self.get_state(host).await;
        let state = state.lock().await;

        BackoffStatus {
            is_throttled: state.is_throttled,
            backoff_exponent: state.backoff_exponent,
            current_backoff: self.calculate_backoff_duration(state.backoff_exponent),
            next_ready_in: state.next_ready_at.checked_duration_since(Instant::now()),
            recent_rate_limits: state.rate_limit_count,
            recent_errors: state.error_count,
            total_recent_requests: state.total_requests,
            ewma_latency_ms: state.ewma_latency.map(|d| d.as_millis() as u64),
        }
    }

    /// Clears all state for a specific host.
    ///
    /// # Arguments
    ///
    /// * `host` - The host to clear state for
    /// Clears all state for a specific host.
    pub async fn clear_host(&self, host: &str) {
        let mut guard = self.hosts.write().await;
        guard.remove(host);
    }

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

    // Private helper methods

    async fn get_state(&self, host: &str) -> Arc<AsyncMutex<HostState>> {
        let mut guard = self.hosts.write().await;
        guard
            .entry(host.to_string())
            .or_insert_with(|| Arc::new(AsyncMutex::new(HostState::default())))
            .clone()
    }

    fn update_latency(&self, state: &mut HostState, elapsed: Duration) {
        // Maintain rolling window of recent latencies
        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) => {
                // EWMA with alpha = 0.2 (favor recent values but smooth)
                current.mul_f64(0.8) + elapsed.mul_f64(0.2)
            }
            None => elapsed,
        });
    }

    fn detect_latency_spike(&self, state: &HostState) -> bool {
        if state.recent_latencies.len() < MIN_SAMPLES_FOR_SPIKE {
            return false;
        }

        // Calculate average of recent latencies (excluding the most recent)
        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();

        // Check if most recent is significantly higher
        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 {
            // If average is effectively zero, use absolute threshold
            most_recent > Duration::from_secs(1)
        }
    }

    fn should_backoff_due_to_errors(&self, state: &HostState) -> bool {
        if state.total_requests < MIN_REQUESTS_FOR_ERROR_RATE {
            return false;
        }

        let error_rate = state.error_count as f64 / state.total_requests as f64;
        error_rate >= ERROR_RATE_THRESHOLD
    }

    fn apply_backoff(&self, state: &mut HostState, is_explicit_rate_limit: bool) {
        // Increase backoff exponent (cap at 10 to prevent excessive delays)
        let previous_exponent = state.backoff_exponent;
        state.backoff_exponent = state.backoff_exponent.saturating_add(1).min(10);

        // Calculate backoff duration
        let base = if is_explicit_rate_limit {
            // Use EWMA latency or default for explicit 429s
            state
                .ewma_latency
                .map_or(DEFAULT_BACKOFF, |latency| latency.max(DEFAULT_BACKOFF))
        } else {
            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;
        }
    }

    fn maybe_reduce_backoff(&self, state: &mut HostState) {
        if state.backoff_exponent > 0 {
            state.backoff_exponent -= 1;
        }

        // If backoff is now zero, clear throttling
        if state.backoff_exponent == 0 {
            state.is_throttled = false;
        }
    }

    fn calculate_backoff_duration(&self, exponent: u32) -> Duration {
        capped_pow2_backoff(DEFAULT_BACKOFF, exponent, MAX_BACKOFF)
    }
}
