// Tests for mecha10_cli::services::redis

use mecha10_cli::services::redis::*;

use serial_test::serial;

#[test]
fn test_service_creation() {
    // Should be able to create with explicit URL
    let result = RedisService::new("redis://localhost:6379");
    assert!(result.is_ok());
}

#[test]
fn test_url_accessor() {
    let service = RedisService::new("redis://localhost:6379").unwrap();
    assert_eq!(service.url(), "redis://localhost:6379");
}

#[test]
fn test_from_env() {
    // Should succeed even if Redis isn't running (just creating client)
    let result = RedisService::from_env();
    assert!(result.is_ok());
}

#[tokio::test]
async fn test_health_check_with_invalid_url() {
    // Create service with invalid URL
    let service = RedisService::new("redis://localhost:9999").unwrap();

    // Health check should fail quickly
    let healthy = service.check_health(Duration::from_millis(100)).await;
    assert!(!healthy);
}

// === New Comprehensive Tests ===

#[test]
fn test_service_creation_with_custom_port() {
    let service = RedisService::new("redis://localhost:6380").unwrap();
    assert_eq!(service.url(), "redis://localhost:6380");
}

#[test]
fn test_service_creation_with_auth() {
    let service = RedisService::new("redis://:password@localhost:6379").unwrap();
    assert_eq!(service.url(), "redis://:password@localhost:6379");
}

#[test]
fn test_service_creation_with_username_and_password() {
    let service = RedisService::new("redis://user:password@localhost:6379").unwrap();
    assert_eq!(service.url(), "redis://user:password@localhost:6379");
}

#[test]
fn test_invalid_url_returns_error() {
    let result = RedisService::new("not-a-valid-url");
    assert!(result.is_err());
    let err_msg = result.unwrap_err().to_string();
    assert!(err_msg.contains("Failed to create Redis client"));
}

#[test]
fn test_empty_url_returns_error() {
    let result = RedisService::new("");
    assert!(result.is_err());
}

#[test]
#[serial]
fn test_from_env_with_mecha10_redis_url() {
    // Test precedence: MECHA10_REDIS_URL should be used first
    std::env::set_var("MECHA10_REDIS_URL", "redis://mecha10:6379");
    std::env::set_var("REDIS_URL", "redis://other:6379");

    let service = RedisService::from_env().unwrap();
    assert_eq!(service.url(), "redis://mecha10:6379");

    std::env::remove_var("MECHA10_REDIS_URL");
    std::env::remove_var("REDIS_URL");
}

#[test]
#[serial]
fn test_from_env_with_redis_url_fallback() {
    // Test precedence: REDIS_URL should be used if MECHA10_REDIS_URL not set
    std::env::remove_var("MECHA10_REDIS_URL");
    std::env::set_var("REDIS_URL", "redis://fallback:6379");

    let service = RedisService::from_env().unwrap();
    assert_eq!(service.url(), "redis://fallback:6379");

    std::env::remove_var("REDIS_URL");
}

#[test]
#[serial]
fn test_from_env_with_default() {
    // Test default when no env vars are set
    std::env::remove_var("MECHA10_REDIS_URL");
    std::env::remove_var("REDIS_URL");

    let service = RedisService::from_env().unwrap();
    assert_eq!(service.url(), "redis://localhost:6379");
}

#[test]
#[serial]
fn test_default_trait_implementation() {
    std::env::remove_var("MECHA10_REDIS_URL");
    std::env::remove_var("REDIS_URL");

    let service = RedisService::default();
    assert_eq!(service.url(), "redis://localhost:6379");
}

#[tokio::test]
async fn test_is_healthy_uses_default_timeout() {
    // is_healthy should use 2 second timeout
    let service = RedisService::new("redis://localhost:9999").unwrap();

    let start = std::time::Instant::now();
    let healthy = service.is_healthy().await;
    let elapsed = start.elapsed();

    assert!(!healthy);
    // Should fail faster than 2 seconds (connection refused is immediate)
    assert!(elapsed < Duration::from_secs(2));
}

#[tokio::test]
async fn test_check_health_respects_timeout() {
    let service = RedisService::new("redis://localhost:9999").unwrap();

    let start = std::time::Instant::now();
    let healthy = service.check_health(Duration::from_millis(50)).await;
    let elapsed = start.elapsed();

    assert!(!healthy);
    // Should respect the short timeout
    assert!(elapsed < Duration::from_millis(200));
}

#[tokio::test]
async fn test_health_check_timeout_triggers() {
    // Use a non-routable IP to force timeout (192.0.2.0/24 is TEST-NET-1)
    let service = RedisService::new("redis://192.0.2.1:6379").unwrap();

    let start = std::time::Instant::now();
    let healthy = service.check_health(Duration::from_millis(100)).await;
    let elapsed = start.elapsed();

    assert!(!healthy);
    // Timeout should trigger around 100ms
    assert!(elapsed >= Duration::from_millis(90));
    assert!(elapsed < Duration::from_millis(500));
}

#[test]
fn test_url_getter_immutable() {
    let service = RedisService::new("redis://localhost:6379").unwrap();
    let url1 = service.url();
    let url2 = service.url();
    assert_eq!(url1, url2);
    assert_eq!(url1, "redis://localhost:6379");
}

#[test]
fn test_service_creation_with_ipv6() {
    let service = RedisService::new("redis://[::1]:6379").unwrap();
    assert_eq!(service.url(), "redis://[::1]:6379");
}

#[test]
fn test_service_creation_with_database_number() {
    let service = RedisService::new("redis://localhost:6379/1").unwrap();
    assert_eq!(service.url(), "redis://localhost:6379/1");
}

#[test]
fn test_service_creation_preserves_url_exactly() {
    let original_url = "redis://user:pass@host:1234/5";
    let service = RedisService::new(original_url).unwrap();
    assert_eq!(service.url(), original_url);
}

// Note: The following tests require a running Redis instance
// They are commented out to avoid test failures in CI/CD

// #[tokio::test]
// async fn test_get_connection_successful() {
//     let service = RedisService::new("redis://localhost:6379").unwrap();
//     let result = service.get_connection().await;
//     assert!(result.is_ok());
// }

// #[tokio::test]
// async fn test_get_multiplexed_connection_successful() {
//     let service = RedisService::new("redis://localhost:6379").unwrap();
//     let result = service.get_multiplexed_connection().await;
//     assert!(result.is_ok());
// }

// #[tokio::test]
// async fn test_health_check_with_running_redis() {
//     let service = RedisService::new("redis://localhost:6379").unwrap();
//     let healthy = service.is_healthy().await;
//     assert!(healthy);
// }
