//! Metrics, observability, and rate-limiting logic.

use std::future::Future;
use std::pin::Pin;
use std::time::Duration;

use tower::{BoxError, ServiceBuilder, limit::RateLimitLayer, service_fn, util::BoxService};

use crate::{Error, Result};

/// A rate limiter built around a tower buffer.
///
/// Tower 0.5 changed `Buffer<S, Request>` (parameterised by Service) to
/// `Buffer<Request, Future>` (parameterised by the response Future).
/// We keep the underlying service type-erased via `BoxService` and pin
/// its Future so the alias matches the new API.
pub type RateGate = tower::buffer::Buffer<
    (),
    Pin<Box<dyn Future<Output = std::result::Result<(), BoxError>> + Send>>,
>;

/// Builds a rate gate to control maximum requests per second.
pub fn build_rate_gate(limit: Option<u32>) -> Result<Option<RateGate>> {
    // Cap configured rate to a sane maximum to avoid large allocations.
    const MAX_RATE_LIMIT_PER_SEC: u32 = 10_000;
    const MAX_RATE_BUFFER_CAP: usize = 2_000;

    let Some(limit_val) = limit else {
        return Ok(None);
    };

    let per_second = limit_val.clamp(1, MAX_RATE_LIMIT_PER_SEC);

    if tokio::runtime::Handle::try_current().is_err() {
        return Err(Error::RateLimiter(
            "Cannot construct rate-limited ScanClient outside of a tokio runtime context".into(),
        ));
    }

    let service = ServiceBuilder::new()
        .layer(RateLimitLayer::new(
            u64::from(per_second),
            Duration::from_secs(1),
        ))
        .service(service_fn(|()| async move { Ok::<(), BoxError>(()) }));

    let boxed = BoxService::new(service);
    // Buffer capacity scaled but clamped to a safe upper bound.
    let buffer_cap = (per_second as usize)
        .saturating_mul(10)
        .min(MAX_RATE_BUFFER_CAP);
    // Tower 0.5: `Buffer::new` spawns the worker internally and returns
    // the Buffer directly (not the (Buffer, Worker) tuple from 0.4).
    let buffer = tower::buffer::Buffer::new(boxed, buffer_cap);
    Ok(Some(buffer))
}
