use crate::{KeyResolver, config::Config, service::RateLimit};
use fred::prelude::LuaInterface;
use std::time::Duration;
use tower::Layer;

/// Enforces rate limit on the underlying service.
///
/// Construct using [`RateLimitLayerBuilder::build`], with the builder obtained from [`RateLimitLayer::builder`].
#[derive(Debug, Clone)]
pub struct RateLimitLayer<L, K>
where
    L: LuaInterface,
    K: KeyResolver,
{
    config: Config<L, K>,
}

impl<S, L, K> Layer<S> for RateLimitLayer<L, K>
where
    L: LuaInterface,
    K: KeyResolver,
{
    type Service = RateLimit<S, L, K>;

    fn layer(&self, inner: S) -> Self::Service {
        RateLimit::new(inner, self.config.clone())
    }
}

impl<L, K> RateLimitLayer<L, K>
where
    L: LuaInterface,
    K: KeyResolver,
{
    /// Returns a new builder for configuring the layer.
    pub fn builder() -> RateLimitLayerBuilder<L, K, false, false, false, false> {
        RateLimitLayerBuilder {
            conn: None,
            key_resolver: None,
            capacity: None,
            emission_interval: None,
            fail_open: false,
        }
    }
}

/// Builder for [`RateLimitLayer`].
///
/// Uses const generics to enforce these required fields are set at compile time:
/// - [`conn`](Self::conn)
/// - [`key_resolver`](Self::key_resolver)
/// - [`capacity`](Self::capacity)
/// - [`emission_interval`](Self::emission_interval)
///
/// Each method consumes `self` and returns a new builder.
/// Clone the builder first if you need to branch configurations.
#[derive(Debug, Clone)]
pub struct RateLimitLayerBuilder<
    L,
    K,
    const CONN: bool,
    const KEY_RESOLVER: bool,
    const CAPACITY: bool,
    const EMISSION_INTERVAL: bool,
> where
    L: LuaInterface,
    K: KeyResolver,
{
    conn: Option<L>,
    key_resolver: Option<K>,
    capacity: Option<u16>,
    emission_interval: Option<u32>,
    fail_open: bool,
}

impl<
    L,
    K,
    const CONN: bool,
    const KEY_RESOLVER: bool,
    const CAPACITY: bool,
    const EMISSION_INTERVAL: bool,
> RateLimitLayerBuilder<L, K, CONN, KEY_RESOLVER, CAPACITY, EMISSION_INTERVAL>
where
    L: LuaInterface,
    K: KeyResolver,
{
    /// Sets the Redis connection.
    pub fn conn(
        self,
        conn: L,
    ) -> RateLimitLayerBuilder<L, K, true, KEY_RESOLVER, CAPACITY, EMISSION_INTERVAL> {
        RateLimitLayerBuilder {
            conn: Some(conn),
            key_resolver: self.key_resolver,
            capacity: self.capacity,
            emission_interval: self.emission_interval,
            fail_open: self.fail_open,
        }
    }

    /// Sets the key resolver.
    pub fn key_resolver(
        self,
        key_resolver: K,
    ) -> RateLimitLayerBuilder<L, K, CONN, true, CAPACITY, EMISSION_INTERVAL> {
        RateLimitLayerBuilder {
            conn: self.conn,
            key_resolver: Some(key_resolver),
            capacity: self.capacity,
            emission_interval: self.emission_interval,
            fail_open: self.fail_open,
        }
    }

    /// Sets the capacity.
    ///
    /// Panics if `capacity == 0`.
    pub fn capacity(
        self,
        capacity: u16,
    ) -> RateLimitLayerBuilder<L, K, CONN, KEY_RESOLVER, true, EMISSION_INTERVAL> {
        assert!(capacity > 0, "capacity cannot be 0");
        RateLimitLayerBuilder {
            conn: self.conn,
            key_resolver: self.key_resolver,
            capacity: Some(capacity),
            emission_interval: self.emission_interval,
            fail_open: self.fail_open,
        }
    }

    /// Sets the emission interval.
    ///
    /// Panics if the duration is less than 1 or greater than `u32::MAX` milliseconds.
    pub fn emission_interval(
        self,
        emission_interval: Duration,
    ) -> RateLimitLayerBuilder<L, K, CONN, KEY_RESOLVER, CAPACITY, true> {
        let ms = emission_interval.as_millis();
        assert!(
            ms >= 1,
            "emission_interval cannot be shorter than 1 millisecond. Got {emission_interval:?}",
        );
        RateLimitLayerBuilder {
            conn: self.conn,
            key_resolver: self.key_resolver,
            capacity: self.capacity,
            emission_interval: Some(ms.try_into().unwrap_or_else(|_| {
                panic!(
                    "per duration is too long. Max allowed is {:?}, got {emission_interval:?}",
                    Duration::from_millis(u32::MAX.into())
                )
            })),
            fail_open: self.fail_open,
        }
    }

    /// Sets whether to allow requests if Redis is unavailable.
    ///
    /// Defaults to `false`.
    pub fn fail_open(
        self,
        fail_open: bool,
    ) -> RateLimitLayerBuilder<L, K, CONN, KEY_RESOLVER, CAPACITY, EMISSION_INTERVAL> {
        RateLimitLayerBuilder {
            conn: self.conn,
            key_resolver: self.key_resolver,
            capacity: self.capacity,
            emission_interval: self.emission_interval,
            fail_open,
        }
    }
}

impl<L, K> RateLimitLayerBuilder<L, K, true, true, true, true>
where
    L: LuaInterface,
    K: KeyResolver,
{
    /// Builds the layer.
    pub fn build(self) -> RateLimitLayer<L, K> {
        RateLimitLayer {
            config: unsafe {
                Config {
                    conn: self.conn.unwrap_unchecked(),
                    key_resolver: self.key_resolver.unwrap_unchecked(),
                    capacity: self.capacity.unwrap_unchecked(),
                    emission_interval: self.emission_interval.unwrap_unchecked(),
                    fail_open: self.fail_open,
                }
            },
        }
    }
}
