#![cfg(feature = "cluster")]
mod support;

#[cfg(test)]
mod cluster {
    use std::sync::{
        atomic::{self, AtomicI32, Ordering},
        Arc,
    };

    use crate::support::*;
    use redis::{
        cluster::{cluster_pipe, ClusterClient},
        cmd, parse_redis_value, Commands, ConnectionLike, ErrorKind, ProtocolVersion, RedisError,
        Value,
    };

    #[test]
    #[serial_test::serial]
    fn test_cluster_basics() {
        let cluster = TestClusterContext::new(3, 0);
        let mut con = cluster.connection();

        redis::cmd("SET")
            .arg("{x}key1")
            .arg(b"foo")
            .execute(&mut con);
        redis::cmd("SET").arg(&["{x}key2", "bar"]).execute(&mut con);

        assert_eq!(
            redis::cmd("MGET")
                .arg(&["{x}key1", "{x}key2"])
                .query(&mut con),
            Ok(("foo".to_string(), b"bar".to_vec()))
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_with_username_and_password() {
        let cluster = TestClusterContext::new_with_cluster_client_builder(
            3,
            0,
            |builder| {
                builder
                    .username(RedisCluster::username().to_string())
                    .password(RedisCluster::password().to_string())
            },
            false,
        );
        cluster.disable_default_user();

        let mut con = cluster.connection();

        redis::cmd("SET")
            .arg("{x}key1")
            .arg(b"foo")
            .execute(&mut con);
        redis::cmd("SET").arg(&["{x}key2", "bar"]).execute(&mut con);

        assert_eq!(
            redis::cmd("MGET")
                .arg(&["{x}key1", "{x}key2"])
                .query(&mut con),
            Ok(("foo".to_string(), b"bar".to_vec()))
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_with_bad_password() {
        let cluster = TestClusterContext::new_with_cluster_client_builder(
            3,
            0,
            |builder| {
                builder
                    .username(RedisCluster::username().to_string())
                    .password("not the right password".to_string())
            },
            false,
        );
        assert!(cluster.client.get_connection(None).is_err());
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_read_from_replicas() {
        let cluster = TestClusterContext::new_with_cluster_client_builder(
            6,
            1,
            |builder| builder.read_from_replicas(),
            false,
        );
        let mut con = cluster.connection();

        // Write commands would go to the primary nodes
        redis::cmd("SET")
            .arg("{x}key1")
            .arg(b"foo")
            .execute(&mut con);
        redis::cmd("SET").arg(&["{x}key2", "bar"]).execute(&mut con);

        // Read commands would go to the replica nodes
        assert_eq!(
            redis::cmd("MGET")
                .arg(&["{x}key1", "{x}key2"])
                .query(&mut con),
            Ok(("foo".to_string(), b"bar".to_vec()))
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_eval() {
        let cluster = TestClusterContext::new(3, 0);
        let mut con = cluster.connection();

        let rv = redis::cmd("EVAL")
            .arg(
                r#"
            redis.call("SET", KEYS[1], "1");
            redis.call("SET", KEYS[2], "2");
            return redis.call("MGET", KEYS[1], KEYS[2]);
        "#,
            )
            .arg("2")
            .arg("{x}a")
            .arg("{x}b")
            .query(&mut con);

        assert_eq!(rv, Ok(("1".to_string(), "2".to_string())));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_resp3() {
        if use_protocol() == ProtocolVersion::RESP2 {
            return;
        }
        let cluster = TestClusterContext::new(3, 0);

        let mut connection = cluster.connection();

        let hello: std::collections::HashMap<String, Value> =
            redis::cmd("HELLO").query(&mut connection).unwrap();
        assert_eq!(hello.get("proto").unwrap(), &Value::Int(3));

        let _: () = connection.hset("hash", "foo", "baz").unwrap();
        let _: () = connection.hset("hash", "bar", "foobar").unwrap();
        let result: Value = connection.hgetall("hash").unwrap();

        assert_eq!(
            result,
            Value::Map(vec![
                (
                    Value::BulkString("foo".as_bytes().to_vec()),
                    Value::BulkString("baz".as_bytes().to_vec())
                ),
                (
                    Value::BulkString("bar".as_bytes().to_vec()),
                    Value::BulkString("foobar".as_bytes().to_vec())
                )
            ])
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_multi_shard_commands() {
        let cluster = TestClusterContext::new(3, 0);

        let mut connection = cluster.connection();

        let res: String = connection
            .mset(&[("foo", "bar"), ("bar", "foo"), ("baz", "bazz")])
            .unwrap();
        assert_eq!(res, "OK");
        let res: Vec<String> = connection.mget(&["baz", "foo", "bar"]).unwrap();
        assert_eq!(res, vec!["bazz", "bar", "foo"]);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_pipeline() {
        let cluster = TestClusterContext::new(3, 0);
        cluster.wait_for_cluster_up();
        let mut con = cluster.connection();

        let resp = cluster_pipe()
            .cmd("SET")
            .arg("key_1")
            .arg(42)
            .query::<Vec<String>>(&mut con)
            .unwrap();

        assert_eq!(resp, vec!["OK".to_string()]);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_pipeline_multiple_keys() {
        use redis::FromRedisValue;
        let cluster = TestClusterContext::new(3, 0);
        cluster.wait_for_cluster_up();
        let mut con = cluster.connection();

        let resp = cluster_pipe()
            .cmd("HSET")
            .arg("hash_1")
            .arg("key_1")
            .arg("value_1")
            .cmd("ZADD")
            .arg("zset")
            .arg(1)
            .arg("zvalue_2")
            .query::<Vec<i64>>(&mut con)
            .unwrap();

        assert_eq!(resp, vec![1i64, 1i64]);

        let resp = cluster_pipe()
            .cmd("HGET")
            .arg("hash_1")
            .arg("key_1")
            .cmd("ZCARD")
            .arg("zset")
            .query::<Vec<redis::Value>>(&mut con)
            .unwrap();

        let resp_1: String = FromRedisValue::from_redis_value(&resp[0]).unwrap();
        assert_eq!(resp_1, "value_1".to_string());

        let resp_2: usize = FromRedisValue::from_redis_value(&resp[1]).unwrap();
        assert_eq!(resp_2, 1);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_pipeline_invalid_command() {
        let cluster = TestClusterContext::new(3, 0);
        cluster.wait_for_cluster_up();
        let mut con = cluster.connection();

        let err = cluster_pipe()
            .cmd("SET")
            .arg("foo")
            .arg(42)
            .ignore()
            .cmd(" SCRIPT kill ")
            .query::<()>(&mut con)
            .unwrap_err();

        assert_eq!(
        err.to_string(),
        "This command cannot be safely routed in cluster mode - ClientError: Command 'SCRIPT KILL' can't be executed in a cluster pipeline."
    );

        let err = cluster_pipe().keys("*").query::<()>(&mut con).unwrap_err();

        assert_eq!(
        err.to_string(),
        "This command cannot be safely routed in cluster mode - ClientError: Command 'KEYS' can't be executed in a cluster pipeline."
    );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_can_connect_to_server_that_sends_cluster_slots_without_host_name() {
        let name = "test_cluster_can_connect_to_server_that_sends_cluster_slots_without_host_name";

        let MockEnv { mut connection, .. } = MockEnv::new(name, move |cmd: &[u8], _| {
            if contains_slice(cmd, b"PING") {
                Err(Ok(Value::SimpleString("OK".into())))
            } else if contains_slice(cmd, b"CLUSTER") && contains_slice(cmd, b"SLOTS") {
                Err(Ok(Value::Array(vec![Value::Array(vec![
                    Value::Int(0),
                    Value::Int(16383),
                    Value::Array(vec![
                        Value::BulkString("".as_bytes().to_vec()),
                        Value::Int(6379),
                    ]),
                ])])))
            } else {
                Err(Ok(Value::Nil))
            }
        });

        let value = cmd("GET").arg("test").query::<Value>(&mut connection);

        assert_eq!(value, Ok(Value::Nil));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_can_connect_to_server_that_sends_cluster_slots_with_null_host_name() {
        let name =
            "test_cluster_can_connect_to_server_that_sends_cluster_slots_with_null_host_name";

        let MockEnv { mut connection, .. } = MockEnv::new(name, move |cmd: &[u8], _| {
            if contains_slice(cmd, b"PING") {
                Err(Ok(Value::SimpleString("OK".into())))
            } else if contains_slice(cmd, b"CLUSTER") && contains_slice(cmd, b"SLOTS") {
                Err(Ok(Value::Array(vec![Value::Array(vec![
                    Value::Int(0),
                    Value::Int(16383),
                    Value::Array(vec![Value::Nil, Value::Int(6379)]),
                ])])))
            } else {
                Err(Ok(Value::Nil))
            }
        });

        let value = cmd("GET").arg("test").query::<Value>(&mut connection);

        assert_eq!(value, Ok(Value::Nil));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_can_connect_to_server_that_sends_cluster_slots_with_partial_nodes_with_unknown_host_name(
    ) {
        let name = "test_cluster_can_connect_to_server_that_sends_cluster_slots_with_partial_nodes_with_unknown_host_name";

        let MockEnv { mut connection, .. } = MockEnv::new(name, move |cmd: &[u8], _| {
            if contains_slice(cmd, b"PING") {
                Err(Ok(Value::SimpleString("OK".into())))
            } else if contains_slice(cmd, b"CLUSTER") && contains_slice(cmd, b"SLOTS") {
                Err(Ok(Value::Array(vec![
                    Value::Array(vec![
                        Value::Int(0),
                        Value::Int(7000),
                        Value::Array(vec![
                            Value::BulkString(name.as_bytes().to_vec()),
                            Value::Int(6379),
                        ]),
                    ]),
                    Value::Array(vec![
                        Value::Int(7001),
                        Value::Int(16383),
                        Value::Array(vec![
                            Value::BulkString("?".as_bytes().to_vec()),
                            Value::Int(6380),
                        ]),
                    ]),
                ])))
            } else {
                Err(Ok(Value::Nil))
            }
        });

        let value = cmd("GET").arg("test").query::<Value>(&mut connection);
        assert_eq!(value, Ok(Value::Nil));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_pipeline_command_ordering() {
        let cluster = TestClusterContext::new(3, 0);
        cluster.wait_for_cluster_up();
        let mut con = cluster.connection();
        let mut pipe = cluster_pipe();

        let mut queries = Vec::new();
        let mut expected = Vec::new();
        for i in 0..100 {
            queries.push(format!("foo{i}"));
            expected.push(format!("bar{i}"));
            pipe.set(&queries[i], &expected[i]).ignore();
        }
        pipe.execute(&mut con);

        pipe.clear();
        for q in &queries {
            pipe.get(q);
        }

        let got = pipe.query::<Vec<String>>(&mut con).unwrap();
        assert_eq!(got, expected);
    }

    #[test]
    #[serial_test::serial]
    #[ignore] // Flaky
    fn test_cluster_pipeline_ordering_with_improper_command() {
        let cluster = TestClusterContext::new(3, 0);
        cluster.wait_for_cluster_up();
        let mut con = cluster.connection();
        let mut pipe = cluster_pipe();

        let mut queries = Vec::new();
        let mut expected = Vec::new();
        for i in 0..10 {
            if i == 5 {
                pipe.cmd("hset").arg("foo").ignore();
            } else {
                let query = format!("foo{i}");
                let r = format!("bar{i}");
                pipe.set(&query, &r).ignore();
                queries.push(query);
                expected.push(r);
            }
        }
        pipe.query::<()>(&mut con).unwrap_err();

        std::thread::sleep(std::time::Duration::from_secs(5));

        pipe.clear();
        for q in &queries {
            pipe.get(q);
        }

        let got = pipe.query::<Vec<String>>(&mut con).unwrap();
        assert_eq!(got, expected);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_retries() {
        let name = "tryagain";

        let requests = atomic::AtomicUsize::new(0);
        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")]).retries(5),
            name,
            move |cmd: &[u8], _| {
                respond_startup(name, cmd)?;

                match requests.fetch_add(1, atomic::Ordering::SeqCst) {
                    0..=4 => Err(parse_redis_value(b"-TRYAGAIN mock\r\n")),
                    _ => Err(Ok(Value::BulkString(b"123".to_vec()))),
                }
            },
        );

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        assert_eq!(value, Ok(Some(123)));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_exhaust_retries() {
        let name = "tryagain_exhaust_retries";

        let requests = Arc::new(atomic::AtomicUsize::new(0));

        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")]).retries(2),
            name,
            {
                let requests = requests.clone();
                move |cmd: &[u8], _| {
                    respond_startup(name, cmd)?;
                    requests.fetch_add(1, atomic::Ordering::SeqCst);
                    Err(parse_redis_value(b"-TRYAGAIN mock\r\n"))
                }
            },
        );

        let result = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        match result {
            Ok(_) => panic!("result should be an error"),
            Err(e) => match e.kind() {
                ErrorKind::TryAgain => {}
                _ => panic!("Expected TryAgain but got {:?}", e.kind()),
            },
        }
        assert_eq!(requests.load(atomic::Ordering::SeqCst), 3);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_move_error_when_new_node_is_added() {
        let name = "rebuild_with_extra_nodes";

        let requests = atomic::AtomicUsize::new(0);
        let started = atomic::AtomicBool::new(false);
        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::new(name, move |cmd: &[u8], port| {
            if !started.load(atomic::Ordering::SeqCst) {
                respond_startup(name, cmd)?;
            }
            started.store(true, atomic::Ordering::SeqCst);

            if contains_slice(cmd, b"PING") {
                return Err(Ok(Value::SimpleString("OK".into())));
            }

            let i = requests.fetch_add(1, atomic::Ordering::SeqCst);

            match i {
                // Respond that the key exists on a node that does not yet have a connection:
                0 => Err(parse_redis_value(b"-MOVED 123\r\n")),
                // Respond with the new masters
                1 => Err(Ok(Value::Array(vec![
                    Value::Array(vec![
                        Value::Int(0),
                        Value::Int(1),
                        Value::Array(vec![
                            Value::BulkString(name.as_bytes().to_vec()),
                            Value::Int(6379),
                        ]),
                    ]),
                    Value::Array(vec![
                        Value::Int(2),
                        Value::Int(16383),
                        Value::Array(vec![
                            Value::BulkString(name.as_bytes().to_vec()),
                            Value::Int(6380),
                        ]),
                    ]),
                ]))),
                _ => {
                    // Check that the correct node receives the request after rebuilding
                    assert_eq!(port, 6380);
                    Err(Ok(Value::BulkString(b"123".to_vec())))
                }
            }
        });

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        assert_eq!(value, Ok(Some(123)));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_ask_redirect() {
        let name = "node";
        let completed = Arc::new(AtomicI32::new(0));
        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")]),
            name,
            {
                move |cmd: &[u8], port| {
                    respond_startup_two_nodes(name, cmd)?;
                    // Error twice with io-error, ensure connection is reestablished w/out calling
                    // other node (i.e., not doing a full slot rebuild)
                    let count = completed.fetch_add(1, Ordering::SeqCst);
                    match port {
                        6379 => match count {
                            0 => Err(parse_redis_value(b"-ASK 14000 node:6380\r\n")),
                            _ => panic!("Node should not be called now"),
                        },
                        6380 => match count {
                            1 => {
                                assert!(contains_slice(cmd, b"ASKING"));
                                Err(Ok(Value::Okay))
                            }
                            2 => {
                                assert!(contains_slice(cmd, b"GET"));
                                Err(Ok(Value::BulkString(b"123".to_vec())))
                            }
                            _ => panic!("Node should not be called now"),
                        },
                        _ => panic!("Wrong node"),
                    }
                }
            },
        );

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        assert_eq!(value, Ok(Some(123)));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_ask_error_when_new_node_is_added() {
        let name = "ask_with_extra_nodes";

        let requests = atomic::AtomicUsize::new(0);
        let started = atomic::AtomicBool::new(false);

        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::new(name, move |cmd: &[u8], port| {
            if !started.load(atomic::Ordering::SeqCst) {
                respond_startup(name, cmd)?;
            }
            started.store(true, atomic::Ordering::SeqCst);

            if contains_slice(cmd, b"PING") {
                return Err(Ok(Value::SimpleString("OK".into())));
            }

            let i = requests.fetch_add(1, atomic::Ordering::SeqCst);

            match i {
                // Respond that the key exists on a node that does not yet have a connection:
                0 => Err(parse_redis_value(
                    format!("-ASK 123 {name}:6380\r\n").as_bytes(),
                )),
                1 => {
                    assert_eq!(port, 6380);
                    assert!(contains_slice(cmd, b"ASKING"));
                    Err(Ok(Value::Okay))
                }
                2 => {
                    assert_eq!(port, 6380);
                    assert!(contains_slice(cmd, b"GET"));
                    Err(Ok(Value::BulkString(b"123".to_vec())))
                }
                _ => {
                    panic!("Unexpected request: {cmd:?}");
                }
            }
        });

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        assert_eq!(value, Ok(Some(123)));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_replica_read() {
        let name = "node";

        // requests should route to replica
        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")])
                .retries(0)
                .read_from_replicas(),
            name,
            move |cmd: &[u8], port| {
                respond_startup_with_replica(name, cmd)?;

                match port {
                    6380 => Err(Ok(Value::BulkString(b"123".to_vec()))),
                    _ => panic!("Wrong node"),
                }
            },
        );

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);
        assert_eq!(value, Ok(Some(123)));

        // requests should route to primary
        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")])
                .retries(0)
                .read_from_replicas(),
            name,
            move |cmd: &[u8], port| {
                respond_startup_with_replica(name, cmd)?;
                match port {
                    6379 => Err(Ok(Value::SimpleString("OK".into()))),
                    _ => panic!("Wrong node"),
                }
            },
        );

        let value = cmd("SET")
            .arg("test")
            .arg("123")
            .query::<Option<Value>>(&mut connection);
        assert_eq!(value, Ok(Some(Value::SimpleString("OK".to_owned()))));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_io_error() {
        let name = "node";
        let completed = Arc::new(AtomicI32::new(0));
        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")]).retries(2),
            name,
            move |cmd: &[u8], port| {
                respond_startup_two_nodes(name, cmd)?;
                // Error twice with io-error, ensure connection is reestablished w/out calling
                // other node (i.e., not doing a full slot rebuild)
                match port {
                    6380 => panic!("Node should not be called"),
                    _ => match completed.fetch_add(1, Ordering::SeqCst) {
                        0..=1 => Err(Err(RedisError::from(std::io::Error::new(
                            std::io::ErrorKind::ConnectionReset,
                            "mock-io-error",
                        )))),
                        _ => Err(Ok(Value::BulkString(b"123".to_vec()))),
                    },
                }
            },
        );

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        assert_eq!(value, Ok(Some(123)));
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_non_retryable_error_should_not_retry() {
        let name = "node";
        let completed = Arc::new(AtomicI32::new(0));
        let MockEnv { mut connection, .. } = MockEnv::new(name, {
            let completed = completed.clone();
            move |cmd: &[u8], _| {
                respond_startup_two_nodes(name, cmd)?;
                // Use TypeError which has NoRetry behavior
                completed.fetch_add(1, Ordering::SeqCst);
                Err(Err((ErrorKind::TypeError, "WRONGTYPE mock error").into()))
            }
        });

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        match value {
            Ok(_) => panic!("result should be an error"),
            Err(e) => match e.kind() {
                ErrorKind::TypeError => {}
                _ => panic!("Expected TypeError but got {:?}", e.kind()),
            },
        }
        assert_eq!(completed.load(Ordering::SeqCst), 1);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_readonly_error_should_refresh_slots_and_retry() {
        let name = "node";
        let requests = Arc::new(AtomicI32::new(0));
        let MockEnv { mut connection, .. } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")]).retries(3),
            name,
            {
                let requests = requests.clone();
                move |cmd: &[u8], _| {
                    respond_startup_two_nodes(name, cmd)?;
                    let count = requests.fetch_add(1, Ordering::SeqCst);
                    match count {
                        // First request fails with ReadOnly (simulating failover)
                        0 => Err(Err((
                            ErrorKind::ReadOnly,
                            "READONLY You can't write against a read only replica",
                        )
                            .into())),
                        // After slot refresh, retry succeeds
                        _ => Err(Ok(Value::BulkString(b"123".to_vec()))),
                    }
                }
            },
        );

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        assert_eq!(value, Ok(Some(123)));
        assert!(
            requests.load(Ordering::SeqCst) >= 2,
            "Should have retried after ReadOnly error"
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_readonly_error_exhausts_retries() {
        let name = "node";
        let requests = Arc::new(AtomicI32::new(0));
        let MockEnv { mut connection, .. } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")]).retries(2),
            name,
            {
                let requests = requests.clone();
                move |cmd: &[u8], _| {
                    respond_startup_two_nodes(name, cmd)?;
                    requests.fetch_add(1, Ordering::SeqCst);
                    // Always return ReadOnly to exhaust retries
                    Err(Err((ErrorKind::ReadOnly, "READONLY").into()))
                }
            },
        );

        let value = cmd("GET").arg("test").query::<Option<i32>>(&mut connection);

        match value {
            Ok(_) => panic!("result should be an error"),
            Err(e) => assert_eq!(e.kind(), ErrorKind::ReadOnly),
        }
        // Initial attempt + 2 retries = 3 total
        assert_eq!(requests.load(Ordering::SeqCst), 3);
    }

    fn test_cluster_fan_out(
        command: &'static str,
        expected_ports: Vec<u16>,
        slots_config: Option<Vec<MockSlotRange>>,
    ) {
        let name = "node";
        let found_ports = Arc::new(std::sync::Mutex::new(Vec::new()));
        let ports_clone = found_ports.clone();
        let mut cmd = redis::Cmd::new();
        for arg in command.split_whitespace() {
            cmd.arg(arg);
        }
        let packed_cmd = cmd.get_packed_command();
        // requests should route to replica
        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")])
                .retries(0)
                .read_from_replicas(),
            name,
            move |received_cmd: &[u8], port| {
                respond_startup_with_replica_using_config(
                    name,
                    received_cmd,
                    slots_config.clone(),
                )?;
                if received_cmd == packed_cmd {
                    ports_clone.lock().unwrap().push(port);
                    return Err(Ok(Value::SimpleString("OK".into())));
                }
                Ok(())
            },
        );

        let _ = cmd.query::<Option<()>>(&mut connection);
        found_ports.lock().unwrap().sort();
        // MockEnv creates 2 mock connections.
        assert_eq!(*found_ports.lock().unwrap(), expected_ports);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_fan_out_to_all_primaries() {
        test_cluster_fan_out("FLUSHALL", vec![6379, 6381], None);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_fan_out_to_all_nodes() {
        test_cluster_fan_out("CONFIG SET", vec![6379, 6380, 6381, 6382], None);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_fan_out_out_once_to_each_primary_when_no_replicas_are_available() {
        test_cluster_fan_out(
            "CONFIG SET",
            vec![6379, 6381],
            Some(vec![
                MockSlotRange {
                    primary_port: 6379,
                    replica_ports: Vec::new(),
                    slot_range: (0..8191),
                },
                MockSlotRange {
                    primary_port: 6381,
                    replica_ports: Vec::new(),
                    slot_range: (8192..16383),
                },
            ]),
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_fan_out_out_once_even_if_primary_has_multiple_slot_ranges() {
        test_cluster_fan_out(
            "CONFIG SET",
            vec![6379, 6380, 6381, 6382],
            Some(vec![
                MockSlotRange {
                    primary_port: 6379,
                    replica_ports: vec![6380],
                    slot_range: (0..4000),
                },
                MockSlotRange {
                    primary_port: 6381,
                    replica_ports: vec![6382],
                    slot_range: (4001..8191),
                },
                MockSlotRange {
                    primary_port: 6379,
                    replica_ports: vec![6380],
                    slot_range: (8192..8200),
                },
                MockSlotRange {
                    primary_port: 6381,
                    replica_ports: vec![6382],
                    slot_range: (8201..16383),
                },
            ]),
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_split_multi_shard_command_and_combine_arrays_of_values() {
        let name = "test_cluster_split_multi_shard_command_and_combine_arrays_of_values";
        let mut cmd = cmd("MGET");
        cmd.arg("foo").arg("bar").arg("baz");
        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")])
                .retries(0)
                .read_from_replicas(),
            name,
            move |received_cmd: &[u8], port| {
                respond_startup_with_replica_using_config(name, received_cmd, None)?;
                let cmd_str = std::str::from_utf8(received_cmd).unwrap();
                let results = ["foo", "bar", "baz"]
                    .iter()
                    .filter_map(|expected_key| {
                        if cmd_str.contains(expected_key) {
                            Some(Value::BulkString(
                                format!("{expected_key}-{port}").into_bytes(),
                            ))
                        } else {
                            None
                        }
                    })
                    .collect();
                Err(Ok(Value::Array(results)))
            },
        );

        let result = cmd.query::<Vec<String>>(&mut connection).unwrap();
        assert_eq!(result, vec!["foo-6382", "bar-6380", "baz-6380"]);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_route_correctly_on_packed_transaction_with_single_node_requests() {
        let name = "test_cluster_route_correctly_on_packed_transaction_with_single_node_requests";
        let mut pipeline = redis::pipe();
        pipeline.atomic().set("foo", "bar").get("foo");
        let packed_pipeline = pipeline.get_packed_pipeline();

        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")])
                .retries(0)
                .read_from_replicas(),
            name,
            move |received_cmd: &[u8], port| {
                respond_startup_with_replica_using_config(name, received_cmd, None)?;
                if port == 6381 {
                    let results = vec![
                        Value::BulkString("OK".as_bytes().to_vec()),
                        Value::BulkString("QUEUED".as_bytes().to_vec()),
                        Value::BulkString("QUEUED".as_bytes().to_vec()),
                        Value::Array(vec![
                            Value::BulkString("OK".as_bytes().to_vec()),
                            Value::BulkString("bar".as_bytes().to_vec()),
                        ]),
                    ];
                    return Err(Ok(Value::Array(results)));
                }
                Err(Err(RedisError::from(std::io::Error::new(
                    std::io::ErrorKind::ConnectionReset,
                    format!("wrong port: {port}"),
                ))))
            },
        );

        let result = connection
            .req_packed_commands(&packed_pipeline, 3, 1)
            .unwrap();
        assert_eq!(
            result,
            vec![
                Value::BulkString("OK".as_bytes().to_vec()),
                Value::BulkString("bar".as_bytes().to_vec()),
            ]
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_route_correctly_on_packed_transaction_with_single_node_requests2() {
        let name = "test_cluster_route_correctly_on_packed_transaction_with_single_node_requests2";
        let mut pipeline = redis::pipe();
        pipeline.atomic().set("foo", "bar").get("foo");
        let packed_pipeline = pipeline.get_packed_pipeline();
        let results = vec![
            Value::BulkString("OK".as_bytes().to_vec()),
            Value::BulkString("QUEUED".as_bytes().to_vec()),
            Value::BulkString("QUEUED".as_bytes().to_vec()),
            Value::Array(vec![
                Value::BulkString("OK".as_bytes().to_vec()),
                Value::BulkString("bar".as_bytes().to_vec()),
            ]),
        ];
        let expected_result = Value::Array(results);
        let cloned_result = expected_result.clone();

        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")])
                .retries(0)
                .read_from_replicas(),
            name,
            move |received_cmd: &[u8], port| {
                respond_startup_with_replica_using_config(name, received_cmd, None)?;
                if port == 6381 {
                    return Err(Ok(cloned_result.clone()));
                }
                Err(Err(RedisError::from(std::io::Error::new(
                    std::io::ErrorKind::ConnectionReset,
                    format!("wrong port: {port}"),
                ))))
            },
        );

        let result = connection.req_packed_command(&packed_pipeline).unwrap();
        assert_eq!(result, expected_result);
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_with_client_name() {
        let cluster = TestClusterContext::new_with_cluster_client_builder(
            3,
            0,
            |builder| builder.client_name(RedisCluster::client_name().to_string()),
            false,
        );
        let mut con = cluster.connection();
        let client_info: String = redis::cmd("CLIENT").arg("INFO").query(&mut con).unwrap();

        let client_attrs = parse_client_info(&client_info);

        assert!(
            client_attrs.contains_key("name"),
            "Could not detect the 'name' attribute in CLIENT INFO output"
        );

        assert_eq!(
            client_attrs["name"],
            RedisCluster::client_name(),
            "Incorrect client name, expecting: {}, got {}",
            RedisCluster::client_name(),
            client_attrs["name"]
        );
    }

    #[test]
    #[serial_test::serial]
    fn test_cluster_can_be_created_with_partial_slot_coverage() {
        let name = "test_cluster_can_be_created_with_partial_slot_coverage";
        let slots_config = Some(vec![
            MockSlotRange {
                primary_port: 6379,
                replica_ports: vec![],
                slot_range: (0..8000),
            },
            MockSlotRange {
                primary_port: 6381,
                replica_ports: vec![],
                slot_range: (8201..16380),
            },
        ]);

        let MockEnv {
            mut connection,
            handler: _handler,
            ..
        } = MockEnv::with_client_builder(
            ClusterClient::builder(vec![&*format!("redis://{name}")])
                .retries(0)
                .read_from_replicas(),
            name,
            move |received_cmd: &[u8], _| {
                respond_startup_with_replica_using_config(
                    name,
                    received_cmd,
                    slots_config.clone(),
                )?;
                Err(Ok(Value::SimpleString("PONG".into())))
            },
        );

        let res = connection.req_command(&redis::cmd("PING"));
        assert!(res.is_ok());
    }

    mod mtls_test {
        use super::*;
        use crate::support::mtls_test::create_cluster_client_from_cluster;
        use redis::ConnectionInfo;

        #[test]
        #[serial_test::serial]
        fn test_cluster_basics_with_mtls() {
            let cluster = TestClusterContext::new_with_mtls(3, 0);

            let client = create_cluster_client_from_cluster(&cluster, true).unwrap();
            let mut con = client.get_connection(None).unwrap();

            redis::cmd("SET")
                .arg("{x}key1")
                .arg(b"foo")
                .execute(&mut con);
            redis::cmd("SET").arg(&["{x}key2", "bar"]).execute(&mut con);

            assert_eq!(
                redis::cmd("MGET")
                    .arg(&["{x}key1", "{x}key2"])
                    .query(&mut con),
                Ok(("foo".to_string(), b"bar".to_vec()))
            );
        }

        #[test]
        #[serial_test::serial]
        fn test_cluster_should_not_connect_without_mtls() {
            let cluster = TestClusterContext::new_with_mtls(3, 0);

            let client = create_cluster_client_from_cluster(&cluster, false).unwrap();
            let connection = client.get_connection(None);

            match cluster.cluster.servers.first().unwrap().connection_info() {
                ConnectionInfo {
                    addr: redis::ConnectionAddr::TcpTls { .. },
                    ..
                } => {
                    if connection.is_ok() {
                        panic!("Must NOT be able to connect without client credentials if server accepts TLS");
                    }
                }
                _ => {
                    if let Err(e) = connection {
                        panic!("Must be able to connect without client credentials if server does NOT accept TLS: {e:?}");
                    }
                }
            }
        }
    }
}
