//! Tests for the Enterprise library

#[cfg(test)]
mod tests {
    use crate::{EnterpriseClient, RestError, Result};
    use wiremock::matchers::{basic_auth, method, path};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    #[tokio::test]
    async fn test_enterprise_client_builder_default() {
        let builder = EnterpriseClient::builder();
        // Builder defaults are tested through build
        let client = builder.username("test").password("test").build();
        assert!(client.is_ok());
    }

    #[tokio::test]
    async fn test_enterprise_client_creation() {
        let result = EnterpriseClient::builder()
            .base_url("https://example.com")
            .username("test_user")
            .password("test_pass")
            .timeout(std::time::Duration::from_secs(10))
            .insecure(false)
            .build();

        // Client should be created successfully
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_enterprise_client_get_request() {
        // Start a background HTTP server on a random local port
        let mock_server = MockServer::start().await;

        // Arrange the behaviour of the MockServer adding a Mock
        Mock::given(method("GET"))
            .and(path("/test"))
            .and(basic_auth("test_user", "test_pass"))
            .respond_with(
                ResponseTemplate::new(200).set_body_json(serde_json::json!({"status": "ok"})),
            )
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("test_user")
            .password("test_pass")
            .timeout(std::time::Duration::from_secs(10))
            .insecure(false)
            .build()
            .unwrap();
        let result: Result<serde_json::Value> = client.get("/test").await;

        assert!(result.is_ok());
        let value = result.unwrap();
        assert_eq!(value["status"], "ok");
    }

    #[tokio::test]
    async fn test_enterprise_client_post_request() {
        let mock_server = MockServer::start().await;

        Mock::given(method("POST"))
            .and(path("/test"))
            .and(basic_auth("test_user", "test_pass"))
            .respond_with(
                ResponseTemplate::new(201).set_body_json(serde_json::json!({"created": true})),
            )
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("test_user")
            .password("test_pass")
            .timeout(std::time::Duration::from_secs(10))
            .insecure(false)
            .build()
            .unwrap();
        let test_data = serde_json::json!({"name": "test"});
        let result: Result<serde_json::Value> = client.post("/test", &test_data).await;

        assert!(result.is_ok());
        let value = result.unwrap();
        assert_eq!(value["created"], true);
    }

    #[tokio::test]
    async fn test_enterprise_client_error_handling() {
        let mock_server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/error"))
            .and(basic_auth("test_user", "test_pass"))
            .respond_with(
                ResponseTemplate::new(404).set_body_json(serde_json::json!({"error": "Not found"})),
            )
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("test_user")
            .password("test_pass")
            .timeout(std::time::Duration::from_secs(10))
            .insecure(false)
            .build()
            .unwrap();
        let result: Result<serde_json::Value> = client.get("/error").await;

        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(
            err.is_not_found(),
            "Expected not found error, got: {:?}",
            err
        );
    }

    #[tokio::test]
    async fn test_enterprise_client_authentication_error() {
        let mock_server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/auth-test"))
            .respond_with(
                ResponseTemplate::new(401)
                    .set_body_json(serde_json::json!({"error": "Unauthorized"})),
            )
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("wrong_user")
            .password("wrong_pass")
            .timeout(std::time::Duration::from_secs(10))
            .insecure(false)
            .build()
            .unwrap();
        let result: Result<serde_json::Value> = client.get("/auth-test").await;

        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(
            err.is_unauthorized(),
            "Expected unauthorized error, got: {:?}",
            err
        );
    }

    #[test]
    fn test_rest_error_display() {
        let err = RestError::AuthenticationFailed;
        assert_eq!(err.to_string(), "Authentication failed");

        let err = RestError::ApiError {
            code: 400,
            message: "Bad request".to_string(),
        };
        assert_eq!(err.to_string(), "API error: Bad request (code: 400)");

        let err = RestError::ConnectionError("Connection refused".to_string());
        assert_eq!(err.to_string(), "Connection error: Connection refused");

        let err = RestError::TlsError("cert validation failed".to_string());
        assert_eq!(
            err.to_string(),
            "TLS certificate error: cert validation failed"
        );
    }

    #[tokio::test]
    async fn test_enterprise_client_delete_request() {
        let mock_server = MockServer::start().await;

        Mock::given(method("DELETE"))
            .and(path("/test/123"))
            .and(basic_auth("test_user", "test_pass"))
            .respond_with(ResponseTemplate::new(204))
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("test_user")
            .password("test_pass")
            .timeout(std::time::Duration::from_secs(10))
            .insecure(false)
            .build()
            .unwrap();
        let result = client.delete("/test/123").await;

        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_database_action_export() {
        let mock_server = MockServer::start().await;

        Mock::given(method("POST"))
            .and(path("/v1/bdbs/1/actions/export"))
            .and(basic_auth("admin", "password"))
            .respond_with(
                ResponseTemplate::new(200)
                    .set_body_json(serde_json::json!({"action_uid": "export-123"})),
            )
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("admin")
            .password("password")
            .build()
            .unwrap();

        let handler = crate::bdb::DatabaseHandler::new(client);
        let result = handler.export(1, "ftp://backup/db1.rdb").await;

        assert!(result.is_ok());
        assert!(result.unwrap().action_uid.is_some());
    }

    #[tokio::test]
    async fn test_database_action_import() {
        let mock_server = MockServer::start().await;

        Mock::given(method("POST"))
            .and(path("/v1/bdbs/1/actions/import"))
            .and(basic_auth("admin", "password"))
            .respond_with(
                ResponseTemplate::new(200)
                    .set_body_json(serde_json::json!({"action_uid": "import-456"})),
            )
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("admin")
            .password("password")
            .build()
            .unwrap();

        let handler = crate::bdb::DatabaseHandler::new(client);
        let result = handler.import(1, "ftp://backup/db1.rdb", true).await;

        assert!(result.is_ok());
        assert!(result.unwrap().action_uid.is_some());
    }

    #[tokio::test]
    async fn test_database_action_backup() {
        let mock_server = MockServer::start().await;

        Mock::given(method("POST"))
            .and(path("/v1/bdbs/1/actions/backup"))
            .and(basic_auth("admin", "password"))
            .respond_with(
                ResponseTemplate::new(200)
                    .set_body_json(serde_json::json!({"backup_uid": "backup-789"})),
            )
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("admin")
            .password("password")
            .build()
            .unwrap();

        let handler = crate::bdb::DatabaseHandler::new(client);
        let result = handler.backup(1).await;

        assert!(result.is_ok());
        assert!(result.unwrap().backup_uid.is_some());
    }

    #[tokio::test]
    async fn test_database_get_shards() {
        let mock_server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/v1/bdbs/1/shards"))
            .and(basic_auth("admin", "password"))
            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
                {"shard_id": 1, "role": "master"},
                {"shard_id": 2, "role": "slave"}
            ])))
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("admin")
            .password("password")
            .build()
            .unwrap();

        let handler = crate::bdb::DatabaseHandler::new(client);
        let result = handler.shards(1).await;

        assert!(result.is_ok());
        let shards = result.unwrap();
        assert!(shards.is_array());
    }

    #[tokio::test]
    async fn test_cluster_join_node() {
        let mock_server = MockServer::start().await;

        Mock::given(method("POST"))
            .and(path("/v1/bootstrap/join"))
            .and(basic_auth("admin", "password"))
            .respond_with(
                ResponseTemplate::new(200).set_body_json(serde_json::json!({"status": "joined"})),
            )
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("admin")
            .password("password")
            .build()
            .unwrap();

        let handler = crate::cluster::ClusterHandler::new(client);
        let result = handler.join_node("192.168.1.10", "admin", "password").await;

        assert!(result.is_ok());
        assert_eq!(result.unwrap()["status"], "joined");
    }

    #[tokio::test]
    async fn test_database_get_endpoints() {
        let mock_server = MockServer::start().await;

        Mock::given(method("GET"))
            .and(path("/v1/bdbs/1/endpoints"))
            .and(basic_auth("admin", "password"))
            .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!([
                {
                    "uid": "endpoint:1:1",
                    "addr": ["192.168.1.10", "192.168.1.11"],
                    "port": 12000,
                    "dns_name": "redis-12000.cluster.local",
                    "proxy_policy": "single",
                    "addr_type": "internal",
                    "oss_cluster_api_preferred_ip_type": "internal"
                },
                {
                    "uid": "endpoint:1:2",
                    "addr": ["10.0.0.5"],
                    "port": 12001,
                    "dns_name": "redis-12001.cluster.local",
                    "proxy_policy": "all-master-shards",
                    "addr_type": "external",
                    "exclude_proxies": [1, 2],
                    "include_proxies": [3, 4, 5]
                }
            ])))
            .mount(&mock_server)
            .await;

        let client = EnterpriseClient::builder()
            .base_url(mock_server.uri())
            .username("admin")
            .password("password")
            .build()
            .unwrap();

        let handler = crate::bdb::DatabaseHandler::new(client);
        let result = handler.endpoints(1).await;

        assert!(result.is_ok());
        let endpoints = result.unwrap();
        assert_eq!(endpoints.len(), 2);

        // Check first endpoint
        assert_eq!(endpoints[0].uid, Some("endpoint:1:1".to_string()));
        assert_eq!(endpoints[0].port, Some(12000));
        assert_eq!(
            endpoints[0].dns_name,
            Some("redis-12000.cluster.local".to_string())
        );
        assert_eq!(endpoints[0].proxy_policy, Some("single".to_string()));
        assert_eq!(endpoints[0].addr_type, Some("internal".to_string()));

        // Check second endpoint
        assert_eq!(endpoints[1].uid, Some("endpoint:1:2".to_string()));
        assert_eq!(endpoints[1].port, Some(12001));
        assert_eq!(
            endpoints[1].proxy_policy,
            Some("all-master-shards".to_string())
        );
        assert_eq!(endpoints[1].exclude_proxies, Some(vec![1, 2]));
        assert_eq!(endpoints[1].include_proxies, Some(vec![3, 4, 5]));
    }

    #[tokio::test]
    async fn test_ca_cert_builder_path_nonexistent() {
        // Test that ca_cert builder method fails for nonexistent path
        let result = EnterpriseClient::builder()
            .base_url("https://example.com")
            .username("test")
            .password("test")
            .ca_cert("/nonexistent/path/ca.pem")
            .build();

        // Should fail because file doesn't exist
        match result {
            Err(e) => assert!(
                e.to_string().contains("Failed to read CA certificate"),
                "Expected CA cert read error, got: {}",
                e
            ),
            Ok(_) => panic!("Expected error for nonexistent CA cert path"),
        }
    }

    #[test]
    fn test_ca_cert_from_env() {
        // Test that REDIS_ENTERPRISE_CA_CERT env var is documented in from_env
        // We can't easily test the actual env var behavior without side effects,
        // but we test that the method exists and handles missing password
        // SAFETY: This test runs single-threaded and only modifies test-specific env vars
        unsafe {
            std::env::remove_var("REDIS_ENTERPRISE_PASSWORD");
            std::env::remove_var("REDIS_ENTERPRISE_CA_CERT");
        }

        let result = EnterpriseClient::from_env();
        assert!(result.is_err()); // Missing password
    }

    #[tokio::test]
    async fn test_url_normalization() {
        // Test various combinations of base URLs and paths to ensure no double slashes
        let test_cases = vec![
            (
                "https://localhost:9443",
                "/v1/cluster",
                "https://localhost:9443/v1/cluster",
            ),
            (
                "https://localhost:9443/",
                "/v1/cluster",
                "https://localhost:9443/v1/cluster",
            ),
            (
                "https://localhost:9443",
                "v1/cluster",
                "https://localhost:9443/v1/cluster",
            ),
            (
                "https://localhost:9443/",
                "v1/cluster",
                "https://localhost:9443/v1/cluster",
            ),
            (
                "https://localhost:9443",
                "/v1/bdbs/1",
                "https://localhost:9443/v1/bdbs/1",
            ),
            (
                "https://localhost:9443/",
                "/v1/bdbs/1",
                "https://localhost:9443/v1/bdbs/1",
            ),
        ];

        for (base_url, test_path, _expected) in test_cases {
            let mock_server = MockServer::start().await;

            // Mock will fail if the URL has double slashes
            Mock::given(method("GET"))
                .and(path(test_path.trim_start_matches('/')))
                .and(basic_auth("test", "test"))
                .respond_with(
                    ResponseTemplate::new(200).set_body_json(serde_json::json!({"ok": true})),
                )
                .mount(&mock_server)
                .await;

            let client = EnterpriseClient::builder()
                .base_url(base_url.replace("https://localhost:9443", &mock_server.uri()))
                .username("test")
                .password("test")
                .build()
                .unwrap();

            let result: Result<serde_json::Value> = client.get(test_path).await;
            assert!(
                result.is_ok(),
                "Failed for base_url: {}, path: {}",
                base_url,
                test_path
            );
        }
    }
}
