#![cfg(feature = "cache-redis")]

use rs_zero::cache_redis::{
    RedisCacheError, RedisClusterRedirect, RedisClusterRedirectKind, RedisClusterRouteAction,
    RedisClusterSlotMap, RedisCommandEvent, RedisCommandEventKind, RedisCommandOutcome,
    classify_redis_error, redis_cluster_hash_tag, redis_cluster_slot,
};

#[test]
fn cluster_slot_matches_redis_known_vectors_and_hash_tags() {
    assert_eq!(redis_cluster_slot("123456789"), 12739);
    assert_eq!(redis_cluster_slot("foo{bar}zap"), redis_cluster_slot("bar"));
    assert_eq!(redis_cluster_slot("{user1000}.following"), 3443);
    assert_eq!(redis_cluster_slot("{user1000}.followers"), 3443);
    assert_eq!(redis_cluster_hash_tag("foo{}{bar}"), "foo{}{bar}");
    assert_eq!(redis_cluster_hash_tag("foo{{bar}}zap"), "{bar");
    assert_eq!(redis_cluster_hash_tag("foo{bar}zap"), "bar");
}

#[test]
fn moved_and_ask_errors_are_parsed_without_losing_target() {
    let moved = RedisClusterRedirect::parse("MOVED 3999 10.0.0.2:6381").expect("moved");
    assert_eq!(moved.kind, RedisClusterRedirectKind::Moved);
    assert_eq!(moved.slot, 3999);
    assert_eq!(moved.endpoint, "10.0.0.2:6381");

    let ask = RedisClusterRedirect::parse("ASK 4000 cache.example:6379").expect("ask");
    assert_eq!(ask.kind, RedisClusterRedirectKind::Ask);
    assert_eq!(ask.slot, 4000);
    assert_eq!(ask.endpoint, "cache.example:6379");

    let error = RedisCacheError::Backend("MOVED 3999 10.0.0.2:6381".to_string());
    assert_eq!(
        classify_redis_error(&error),
        RedisCommandOutcome::Redirect(RedisClusterRedirectKind::Moved)
    );
    assert!(RedisClusterRedirect::parse("MOVED missing").is_none());
}

#[test]
fn route_action_updates_slot_map_for_moved_and_keeps_ask_temporary() {
    let mut map = RedisClusterSlotMap::default();
    let moved = RedisClusterRedirect::parse("MOVED 3999 10.0.0.2:6381").expect("moved");
    let ask = RedisClusterRedirect::parse("ASK 4000 10.0.0.3:6381").expect("ask");

    assert_eq!(
        map.apply_redirect(&moved),
        RedisClusterRouteAction::RefreshSlot
    );
    assert_eq!(map.endpoint_for_slot(3999), Some("10.0.0.2:6381"));

    assert_eq!(
        map.apply_redirect(&ask),
        RedisClusterRouteAction::AskRedirect
    );
    assert_eq!(map.endpoint_for_slot(4000), None);
}

#[test]
fn redirect_limit_returns_explicit_error() {
    let error = RedisCacheError::too_many_cluster_redirects(5, "rs-zero:{users}:42");

    assert!(
        error
            .to_string()
            .contains("too many redis cluster redirects")
    );
    assert_eq!(classify_redis_error(&error), RedisCommandOutcome::Error);
    assert!(!error.to_string().contains("password"));
}

#[test]
fn command_events_are_low_cardinality() {
    let redirect = RedisCommandEvent::redirect(RedisClusterRedirectKind::Ask);
    let pool = RedisCommandEvent::new(RedisCommandEventKind::Pool, RedisCommandOutcome::Success);

    assert_eq!(redirect.event.as_str(), "redirect");
    assert_eq!(redirect.outcome.as_str(), "ask");
    assert_eq!(pool.event.as_str(), "pool");
    assert_eq!(pool.outcome.as_str(), "success");
}

#[test]
fn cluster_config_builds_native_cluster_client_without_connecting() {
    let config = rs_zero::cache_redis::RedisCacheConfig {
        url: "redis://127.0.0.1:7000, redis://127.0.0.1:7001".to_string(),
        cluster: rs_zero::cache_redis::RedisClusterConfig {
            enabled: true,
            max_redirects: 8,
            read_from_replicas: true,
        },
        ..rs_zero::cache_redis::RedisCacheConfig::default()
    };

    let store = rs_zero::cache_redis::RedisCacheStore::new(config).expect("cluster store");

    assert_eq!(store.namespace(), "rs-zero");
    assert!(format!("{store:?}").contains("RedisCacheBackend::Cluster"));
}

#[test]
fn cluster_startup_nodes_parse_comma_separated_urls() {
    let nodes = rs_zero::cache_redis::cluster_startup_nodes(
        "redis://127.0.0.1:7000, redis://127.0.0.1:7001",
    )
    .expect("nodes");

    assert_eq!(
        nodes,
        vec!["redis://127.0.0.1:7000", "redis://127.0.0.1:7001"]
    );
}
