use alloy::primitives::{Address, Bytes, B256};
use newton_data_provider::{DataProviderBuilder, DataProviderConfig, PolicyTaskDataCache, RedisCacheError};
use newton_prover_core::{
    config::{rpc::ANVIL_CHAIN_ID, NewtonAvsConfigBuilder},
    newton_prover_task_manager::NewtonMessage,
};
use std::time::SystemTime;
use tracing::info;

/// Test Redis cache connection and basic operations
#[tokio::test]
async fn test_data_cache_connection() {
    // Create Redis cache instance
    let mut cache = PolicyTaskDataCache::new("redis://:redis@127.0.0.1:6379")
        .unwrap_or_else(|_| panic!("Failed to create Redis cache"));

    // Initialize connection
    let init_result = cache.initialize().await;

    if init_result.is_err() {
        info!("Redis server not available, skipping Data cache tests");
        return;
    }

    // Test availability check
    assert!(
        cache.is_available().await,
        "Data cache should be available after initialization"
    );
}

/// Test caching and retrieval of policy task data
#[tokio::test]
async fn test_policy_task_data_cache_operations() {
    let mut cache = PolicyTaskDataCache::new("redis://:redis@127.0.0.1:6379")
        .unwrap_or_else(|_| panic!("Failed to create Redis cache"));

    if cache.initialize().await.is_err() {
        info!("Redis server not available, skipping cache operations test");
        return;
    }

    // Create test data
    let policy_address = Address::from([0x1u8; 20]);
    let policy_client_address = Address::from([0x9u8; 20]);
    let policy_data_address = Address::from([0x2u8; 20]);
    let policy_id = B256::from([0x3u8; 32]);
    let chain_id = 31337u64;

    let test_policy_data = NewtonMessage::PolicyData {
        data: Bytes::from(b"test policy data".as_slice()),
        attestation: Bytes::from(b"test attestation".as_slice()),
        policyDataAddress: policy_data_address,
        expireBlock: 1000,
        wasmArgs: Bytes::default(),
    };

    let test_policy_task_data = NewtonMessage::PolicyTaskData {
        policyId: policy_id,
        policyAddress: policy_address,
        policy: Bytes::from("test policy"),
        policyData: vec![test_policy_data],
    };

    // Test SET operation
    let set_result = cache
        .set(
            policy_client_address,
            policy_data_address,
            None,
            test_policy_task_data.clone(),
            100, // expire after 100 blocks
            chain_id,
        )
        .await;

    assert!(
        set_result.is_ok(),
        "Failed to set data in cache: {:?}",
        set_result.err()
    );

    // Test GET operation (should succeed)
    let get_result = cache
        .get(policy_client_address, policy_data_address, chain_id, None)
        .await;
    assert!(
        get_result.is_ok(),
        "Failed to get data from cache: {:?}",
        get_result.err()
    );

    let retrieved_data = get_result.unwrap();
    assert_eq!(retrieved_data.policyId, test_policy_task_data.policyId);
    assert_eq!(retrieved_data.policyAddress, test_policy_task_data.policyAddress);
    assert_eq!(retrieved_data.policy, test_policy_task_data.policy);

    // Test DELETE operation
    let delete_result = cache
        .delete(policy_client_address, policy_data_address, chain_id, None)
        .await;
    assert!(
        delete_result.is_ok(),
        "Failed to delete data from cache: {:?}",
        delete_result.err()
    );

    // Test GET after DELETE (should fail with CacheMiss)
    let get_after_delete = cache
        .get(policy_client_address, policy_data_address, chain_id, None)
        .await;
    assert!(matches!(get_after_delete.err(), Some(RedisCacheError::CacheMiss)));
}

/// Test cache expiration functionality
#[tokio::test]
async fn test_cache_expiration() {
    let mut cache = PolicyTaskDataCache::new("redis://:redis@127.0.0.1:6379")
        .unwrap_or_else(|_| panic!("Failed to create Redis cache"));

    if cache.initialize().await.is_err() {
        info!("Redis server not available, skipping expiration test");
        return;
    }

    let policy_data_address = Address::from([0x4u8; 20]);
    let policy_client_address = Address::from([0x9u8; 20]);
    let chain_id = 31337u64;

    let test_policy_task_data = NewtonMessage::PolicyTaskData {
        policyId: B256::from([0x5u8; 32]),
        policyAddress: Address::from([0x6u8; 20]),
        policy: Bytes::from("expiration test policy"),
        policyData: vec![],
    };

    // Set data with very short expiration (1 block, which is 1 second for chain 31337)
    let set_result = cache
        .set(
            policy_client_address,
            policy_data_address,
            None,
            test_policy_task_data.clone(),
            1, // expire after 1 block
            chain_id,
        )
        .await;

    assert!(set_result.is_ok(), "Failed to set data in cache");

    // Wait for expiration (2 seconds to be safe)
    tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;

    // Try to get expired data (should return CacheMiss)
    let get_result = cache
        .get(policy_client_address, policy_data_address, chain_id, None)
        .await;
    assert!(
        matches!(get_result.as_ref().err(), Some(RedisCacheError::CacheMiss)),
        "Expected CacheMiss for expired data, got: {:?}",
        get_result
    );
}

/// Test cache key generation for different chains
#[tokio::test]
async fn test_cache_key_generation() {
    let mut cache = PolicyTaskDataCache::new("redis://:redis@127.0.0.1:6379")
        .unwrap_or_else(|_| panic!("Failed to create Redis cache"));

    if cache.initialize().await.is_err() {
        info!("Redis server not available, skipping cache key test");
        return;
    }

    let policy_data_address = Address::from([0x7u8; 20]);
    let policy_client_address = Address::from([0x9u8; 20]);
    let test_policy_task_data = NewtonMessage::PolicyTaskData {
        policyId: B256::from([0x8u8; 32]),
        policyAddress: Address::from([0x9u8; 20]),
        policy: Bytes::from("key test policy"),
        policyData: vec![],
    };

    // Set data for different chains
    let chain_id_1 = 1u64; // Ethereum mainnet
    let chain_id_2 = 11155111u64; // Sepolia testnet

    // Set for chain 1
    cache
        .set(
            policy_client_address,
            policy_data_address,
            None,
            test_policy_task_data.clone(),
            100,
            chain_id_1,
        )
        .await
        .expect("Failed to set for chain 1");

    // Set for chain 2
    cache
        .set(
            policy_client_address,
            policy_data_address,
            None,
            test_policy_task_data.clone(),
            100,
            chain_id_2,
        )
        .await
        .expect("Failed to set for chain 2");

    // Verify both can be retrieved independently
    let data_chain_1 = cache
        .get(policy_client_address, policy_data_address, chain_id_1, None)
        .await
        .expect("Failed to get data for chain 1");
    let data_chain_2 = cache
        .get(policy_client_address, policy_data_address, chain_id_2, None)
        .await
        .expect("Failed to get data for chain 2");

    assert_eq!(data_chain_1.policyId, test_policy_task_data.policyId);
    assert_eq!(data_chain_2.policyId, test_policy_task_data.policyId);

    // Clean up
    cache
        .delete(policy_client_address, policy_data_address, chain_id_1, None)
        .await
        .expect("Failed to delete chain 1 data");
    cache
        .delete(policy_client_address, policy_data_address, chain_id_2, None)
        .await
        .expect("Failed to delete chain 2 data");
}

/// Test DataProvider integration with Redis cache
#[tokio::test]
async fn test_data_provider_with_data_cache() {
    // Find the workspace root by looking for Cargo.toml with [workspace]
    let current_dir = std::env::current_dir().unwrap();
    let workspace_root = current_dir
        .ancestors()
        .find(|p| {
            p.join("Cargo.toml").exists()
                && std::fs::read_to_string(p.join("Cargo.toml"))
                    .map(|c| c.contains("[workspace]"))
                    .unwrap_or(false)
        })
        .expect("Failed to find workspace root");

    let config_path = workspace_root.join("crates/data-provider/data-provider.toml");
    info!("Loading config from: {}", config_path.display());

    // Create a test config using absolute path
    let config = NewtonAvsConfigBuilder::new(ANVIL_CHAIN_ID)
        .with_service_path(config_path)
        .build::<DataProviderConfig>()
        .unwrap();

    // Build DataProvider with Data cache
    let mut builder = DataProviderBuilder::new(config);
    builder.with_cache("redis://:redis@127.0.0.1:6379");

    let build_result = builder.build().await;
    assert!(
        build_result.is_ok(),
        "Failed to build DataProvider with Data cache: {:?}",
        build_result.err()
    );

    let mut data_provider = build_result.unwrap();

    // Test initialization
    let init_result = data_provider.initialize().await;

    if init_result.is_err() {
        info!("Redis server not available or initialization failed, skipping DataProvider Data cache test");
        return;
    }

    info!("DataProvider with Redis cache initialized successfully");
}

/// Test cache operations without initialization
#[tokio::test]
async fn test_cache_without_initialization() {
    let cache = PolicyTaskDataCache::new("redis://:redis@127.0.0.1:6379").expect("Failed to create Redis cache");

    let policy_data_address = Address::from([0xau8; 20]);
    let policy_client_address = Address::from([0x9u8; 20]);
    let chain_id = 31337u64;

    let test_policy_task_data = NewtonMessage::PolicyTaskData {
        policyId: B256::from([0xbu8; 32]),
        policyAddress: Address::from([0xcu8; 20]),
        policy: Bytes::from("uninitialized test"),
        policyData: vec![],
    };

    // Try to set without initialization (should fail)
    let set_result = cache
        .set(
            policy_client_address,
            policy_data_address,
            None,
            test_policy_task_data,
            100,
            chain_id,
        )
        .await;
    assert!(matches!(set_result.err(), Some(RedisCacheError::ConnectionError(_))));

    // Try to get without initialization (should fail)
    let get_result = cache
        .get(policy_client_address, policy_data_address, chain_id, None)
        .await;
    assert!(matches!(get_result.err(), Some(RedisCacheError::ConnectionError(_))));

    // Try to delete without initialization (should fail)
    let delete_result = cache
        .delete(policy_client_address, policy_data_address, chain_id, None)
        .await;
    assert!(matches!(delete_result.err(), Some(RedisCacheError::ConnectionError(_))));

    // Check availability without initialization (should return false)
    assert!(!cache.is_available().await);
}

/// Benchmark cache performance (basic test)
#[tokio::test]
async fn test_cache_performance_basic() {
    let mut cache = PolicyTaskDataCache::new("redis://:redis@127.0.0.1:6379").expect("Failed to create Redis cache");

    if cache.initialize().await.is_err() {
        info!("Redis server not available, skipping performance test");
        return;
    }

    let start_time = SystemTime::now();
    let chain_id = 31337u64;
    let policy_client_address = Address::from([0x9u8; 20]);

    // Perform 10 cache operations
    for i in 0..10 {
        let policy_data_address = Address::from([i as u8; 20]);
        let test_data = NewtonMessage::PolicyTaskData {
            policyId: B256::from([i as u8; 32]),
            policyAddress: Address::from([(i + 1) as u8; 20]),
            policy: Bytes::from(format!("performance test policy {}", i).into_bytes()),
            policyData: vec![],
        };

        // Set data
        cache
            .set(
                policy_client_address,
                policy_data_address,
                None,
                test_data.clone(),
                100,
                chain_id,
            )
            .await
            .unwrap_or_else(|_| panic!("Failed to set data for iteration {}", i));

        // Get data
        cache
            .get(policy_client_address, policy_data_address, chain_id, None)
            .await
            .unwrap_or_else(|_| panic!("Failed to get data for iteration {}", i));

        // Delete data
        cache
            .delete(policy_client_address, policy_data_address, chain_id, None)
            .await
            .unwrap_or_else(|_| panic!("Failed to delete data for iteration {}", i));
    }

    let elapsed = start_time.elapsed().unwrap();
    info!("10 cache operations completed in {:?}", elapsed);

    // Basic assertion - should complete within reasonable time
    assert!(elapsed.as_secs() < 10, "Cache operations took too long: {:?}", elapsed);
}

#[tokio::test]
async fn test_redis_url_parsing() {
    use redis::{AsyncCommands, Client};

    // Test the URL format from config
    let url = "redis://:redis@127.0.0.1:6379";
    info!("Testing Redis URL: {}", url);

    match Client::open(url) {
        Ok(client) => {
            info!("Client created successfully");
            info!("Connection info: {:?}", client.get_connection_info());

            // Test connection with timeout using connection manager
            let connection_result =
                tokio::time::timeout(std::time::Duration::from_secs(5), client.get_connection_manager()).await;

            match connection_result {
                Ok(Ok(mut conn)) => {
                    info!("Connection established successfully");

                    // Test a simple ping
                    match conn.get::<&str, Option<String>>("test_key").await {
                        Ok(result) => info!("GET test_key: {:?}", result),
                        Err(e) => info!("GET error: {}", e),
                    }
                }
                Ok(Err(e)) => info!("Connection error: {}", e),
                Err(_) => info!("Connection timed out"),
            }
        }
        Err(e) => {
            info!("Failed to create Redis client: {}", e);
        }
    }
}

#[tokio::test]
async fn test_policy_task_data_cache_initialize() {
    use newton_data_provider::data_cache::PolicyTaskDataCache;

    // Test the URL format from config
    let url = "redis://:redis@127.0.0.1:6379";
    info!("Testing PolicyTaskDataCache initialization with URL: {}", url);

    match PolicyTaskDataCache::new(url) {
        Ok(mut cache) => {
            info!("PolicyTaskDataCache created successfully");

            // Test initialization with timeout
            let init_result = tokio::time::timeout(std::time::Duration::from_secs(5), cache.initialize()).await;

            match init_result {
                Ok(Ok(())) => {
                    info!("PolicyTaskDataCache initialized successfully");

                    // Test if cache is available
                    let is_available = cache.is_available().await;
                    info!("Cache is available: {}", is_available);
                }
                Ok(Err(e)) => info!("PolicyTaskDataCache initialization error: {}", e),
                Err(_) => info!("PolicyTaskDataCache initialization timed out"),
            }
        }
        Err(e) => {
            info!("Failed to create PolicyTaskDataCache: {}", e);
        }
    }
}

#[tokio::test]
async fn test_data_provider_initialization() {
    use newton_data_provider::{DataProviderBuilder, DataProviderConfig};

    // Find the workspace root by looking for Cargo.toml with [workspace]
    let current_dir = std::env::current_dir().unwrap();
    let workspace_root = current_dir
        .ancestors()
        .find(|p| {
            p.join("Cargo.toml").exists()
                && std::fs::read_to_string(p.join("Cargo.toml"))
                    .map(|c| c.contains("[workspace]"))
                    .unwrap_or(false)
        })
        .expect("Failed to find workspace root");

    let config_path = workspace_root.join("crates/data-provider/data-provider.toml");
    info!("Loading config from: {}", config_path.display());

    // Load the actual config using absolute path
    let config = NewtonAvsConfigBuilder::new(ANVIL_CHAIN_ID)
        .with_service_path(config_path)
        .build::<DataProviderConfig>()
        .unwrap();

    info!("Config loaded successfully");
    let cache_url = config.service.data_cache_url.clone();
    info!("Data cache URL: {}", cache_url);

    let mut builder = DataProviderBuilder::new(config);
    // Use the URL from config instead of overriding it
    builder.with_cache(&cache_url);

    info!("Building DataProvider...");
    let build_result = tokio::time::timeout(std::time::Duration::from_secs(10), builder.build()).await;

    match build_result {
        Ok(Ok(mut data_provider)) => {
            info!("DataProvider built successfully");

            info!("Initializing DataProvider...");
            let init_result =
                tokio::time::timeout(std::time::Duration::from_secs(10), data_provider.initialize()).await;

            match init_result {
                Ok(Ok(())) => info!("DataProvider initialized successfully"),
                Ok(Err(e)) => info!("DataProvider initialization error: {}", e),
                Err(_) => info!("DataProvider initialization timed out"),
            }
        }
        Ok(Err(e)) => info!("DataProvider build error: {}", e),
        Err(_) => info!("DataProvider build timed out"),
    }
}
