use crate::{
    cache::CacheKey,
    cache_redis::{RedisCacheError, RedisCacheResult},
};

/// Redis Cluster routing helper for cache keys.
#[derive(Debug, Clone)]
pub struct RedisClusterRouter {
    max_redirects: u32,
    slots: crate::cache_redis::RedisClusterSlotMap,
}

impl RedisClusterRouter {
    /// Creates a router with an empty slot map.
    pub fn new(max_redirects: u32) -> Self {
        Self {
            max_redirects: max_redirects.max(1),
            slots: crate::cache_redis::RedisClusterSlotMap::new(),
        }
    }

    /// Returns the Redis Cluster slot for a cache key.
    pub fn slot_for_key(key: &CacheKey) -> u16 {
        crate::cache_redis::redis_cluster_slot(&key.render())
    }

    /// Returns the currently mapped endpoint for a cache key.
    pub fn endpoint_for_key(&self, key: &CacheKey) -> Option<&str> {
        self.slots.endpoint_for_slot(Self::slot_for_key(key))
    }

    /// Applies a Redis Cluster redirection.
    pub fn apply_redirect(
        &mut self,
        redirect: &crate::cache_redis::RedisClusterRedirect,
    ) -> crate::cache_redis::RedisClusterRouteAction {
        self.slots.apply_redirect(redirect)
    }

    /// Validates a redirect attempt count.
    pub fn ensure_redirect_allowed(&self, attempts: u32, key: &CacheKey) -> RedisCacheResult<()> {
        if attempts > self.max_redirects {
            return Err(RedisCacheError::too_many_cluster_redirects(
                attempts,
                &key.render(),
            ));
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        cache::CacheKey,
        cache_redis::{RedisClusterRedirect, RedisClusterRouter},
    };

    #[test]
    fn cluster_router_updates_moved_slot_mapping() {
        let key = CacheKey::new("rs-zero", ["users", "42"]);
        let mut router = RedisClusterRouter::new(2);
        let redirect = RedisClusterRedirect::parse(&format!(
            "MOVED {} 10.0.0.2:6379",
            RedisClusterRouter::slot_for_key(&key)
        ))
        .expect("redirect");

        router.apply_redirect(&redirect);

        assert_eq!(router.endpoint_for_key(&key), Some("10.0.0.2:6379"));
        assert!(router.ensure_redirect_allowed(3, &key).is_err());
    }
}
