//! Per-IP rate limiting middleware (P5.1 Security Hardening).
//!
//! This middleware prevents metrics flooding abuse by limiting requests per IP address.
//! Applied only to the `/v1/health` endpoint to prevent it from being used for metrics scraping attacks.

use axum::{
    extract::{ConnectInfo, Request, State},
    http::StatusCode,
    middleware::Next,
    response::{IntoResponse, Response},
    Json,
};
use dashmap::DashMap;
use serde::Serialize;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tracing::warn;

/// Rate limiter state tracking per-IP request times.
#[derive(Clone)]
pub struct RateLimitState {
    /// IP address -> last request time
    requests: Arc<DashMap<String, Instant>>,
    /// Minimum interval between requests (default: 1 second)
    interval: Duration,
}

impl RateLimitState {
    /// Create a new rate limiter with the given interval.
    pub fn new(interval: Duration) -> Self {
        Self { requests: Arc::new(DashMap::new()), interval }
    }

    /// Create a rate limiter that allows 1 request per second per IP.
    pub fn one_per_second() -> Self {
        Self::new(Duration::from_secs(1))
    }
}

/// Error response for rate limit exceeded.
#[derive(Debug, Serialize)]
struct RateLimitError {
    error: String,
    code: String,
    retry_after_secs: u64,
}

/// Rate limiting middleware.
///
/// Tracks request times per IP address and rejects requests that come too quickly.
/// Returns 429 Too Many Requests if the IP exceeds the rate limit.
pub async fn rate_limit_middleware(
    connect_info: Option<ConnectInfo<SocketAddr>>,
    State(rate_limit): State<RateLimitState>,
    request: Request,
    next: Next,
) -> Result<Response, impl IntoResponse> {
    let ip = connect_info
        .map(|ConnectInfo(addr)| addr.ip().to_string())
        .unwrap_or_else(|| "unknown".to_string());
    let now = Instant::now();

    // Check if request is allowed
    if let Some(mut entry) = rate_limit.requests.get_mut(&ip) {
        let last_request = *entry;
        let elapsed = now.duration_since(last_request);

        if elapsed < rate_limit.interval {
            // Too fast - reject
            let retry_after = (rate_limit.interval - elapsed).as_secs() + 1;
            warn!(ip = %ip, "Rate limit exceeded for /v1/health");

            // P5.1: Increment rate limit rejection metric
            metrics::counter!("stemedb_rate_limit_rejections_total", "endpoint" => "/v1/health")
                .increment(1);

            let error = RateLimitError {
                error: format!(
                    "Rate limit exceeded. Maximum 1 request per {} seconds per IP.",
                    rate_limit.interval.as_secs()
                ),
                code: "RATE_LIMITED".to_string(),
                retry_after_secs: retry_after,
            };

            return Err((StatusCode::TOO_MANY_REQUESTS, Json(error)));
        }

        // Update last request time
        *entry = now;
    } else {
        // First request from this IP
        rate_limit.requests.insert(ip, now);
    }

    Ok(next.run(request).await)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_rate_limit_state_creation() {
        let state = RateLimitState::one_per_second();
        assert_eq!(state.interval, Duration::from_secs(1));
    }

    #[test]
    fn test_rate_limit_state_custom_interval() {
        let state = RateLimitState::new(Duration::from_secs(5));
        assert_eq!(state.interval, Duration::from_secs(5));
    }
}
