// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use rustfs_ahm::scanner::{
    io_throttler::MetricsSnapshot,
    local_stats::StatsSummary,
    node_scanner::{LoadLevel, NodeScanner, NodeScannerConfig},
    stats_aggregator::{DecentralizedStatsAggregator, DecentralizedStatsAggregatorConfig, NodeInfo},
};
use scanner_optimization_tests::{PerformanceBenchmark, create_test_scanner};
use std::{sync::Arc, time::Duration};
use tempfile::TempDir;
mod scanner_optimization_tests;
#[tokio::test]
async fn test_end_to_end_scanner_lifecycle() {
    let temp_dir = TempDir::new().unwrap();
    let scanner = create_test_scanner(&temp_dir).await;

    scanner.initialize_stats().await.expect("Failed to initialize stats");

    let initial_progress = scanner.get_scan_progress().await;
    assert_eq!(initial_progress.current_cycle, 0);

    scanner.force_save_checkpoint().await.expect("Failed to save checkpoint");

    let checkpoint_info = scanner.get_checkpoint_info().await.unwrap();
    assert!(checkpoint_info.is_some());
}

#[tokio::test]
async fn test_load_balancing_and_throttling_integration() {
    let temp_dir = TempDir::new().unwrap();
    let scanner = create_test_scanner(&temp_dir).await;

    let io_monitor = scanner.get_io_monitor();
    let throttler = scanner.get_io_throttler();

    // Start IO monitoring
    io_monitor.start().await.expect("Failed to start IO monitor");

    // Simulate load variation scenarios
    let load_scenarios = vec![
        (LoadLevel::Low, 10, 100, 0, 5), // (load level, latency, QPS, error rate, connections)
        (LoadLevel::Medium, 30, 300, 10, 20),
        (LoadLevel::High, 80, 800, 50, 50),
        (LoadLevel::Critical, 200, 1200, 100, 100),
    ];

    for (expected_level, latency, qps, error_rate, connections) in load_scenarios {
        // Update business metrics
        scanner.update_business_metrics(latency, qps, error_rate, connections).await;

        // Wait for monitoring system response
        tokio::time::sleep(Duration::from_millis(1200)).await;

        // Get current load level
        let current_level = io_monitor.get_business_load_level().await;

        // Get throttling decision
        let metrics_snapshot = MetricsSnapshot {
            iops: 100 + qps / 10,
            latency,
            cpu_usage: std::cmp::min(50 + (qps / 20) as u8, 100),
            memory_usage: 40,
        };

        let decision = throttler.make_throttle_decision(current_level, Some(metrics_snapshot)).await;

        println!(
            "Load scenario test: Expected={:?}, Actual={:?}, Should_pause={}, Delay={:?}",
            expected_level, current_level, decision.should_pause, decision.suggested_delay
        );

        // Verify throttling effect under high load
        if matches!(current_level, LoadLevel::High | LoadLevel::Critical) {
            assert!(decision.suggested_delay > Duration::from_millis(1000));
        }

        if matches!(current_level, LoadLevel::Critical) {
            assert!(decision.should_pause);
        }
    }

    io_monitor.stop().await;
}

#[tokio::test]
async fn test_checkpoint_resume_functionality() {
    let temp_dir = TempDir::new().unwrap();

    // Create first scanner instance
    let scanner1 = {
        let config = NodeScannerConfig {
            data_dir: temp_dir.path().to_path_buf(),
            ..Default::default()
        };
        NodeScanner::new("checkpoint-test-node".to_string(), config)
    };

    // Initialize and simulate some scan progress
    scanner1.initialize_stats().await.unwrap();

    // Simulate scan progress
    scanner1
        .update_scan_progress_for_test(3, 1, Some("checkpoint-test-key".to_string()))
        .await;

    // Save checkpoint
    scanner1.force_save_checkpoint().await.unwrap();

    // Stop first scanner
    scanner1.stop().await.unwrap();

    // Create second scanner instance (simulate restart)
    let scanner2 = {
        let config = NodeScannerConfig {
            data_dir: temp_dir.path().to_path_buf(),
            ..Default::default()
        };
        NodeScanner::new("checkpoint-test-node".to_string(), config)
    };

    // Try to recover from checkpoint
    scanner2.start_with_resume().await.unwrap();

    // Verify recovered progress
    let recovered_progress = scanner2.get_scan_progress().await;
    assert_eq!(recovered_progress.current_cycle, 3);
    assert_eq!(recovered_progress.current_disk_index, 1);
    assert_eq!(recovered_progress.last_scan_key, Some("checkpoint-test-key".to_string()));

    // Cleanup
    scanner2.cleanup_checkpoint().await.unwrap();
}

#[tokio::test]
async fn test_distributed_stats_aggregation() {
    // Create decentralized stats aggregator
    let config = DecentralizedStatsAggregatorConfig {
        cache_ttl: Duration::from_secs(10), // Increase cache TTL to ensure cache is valid during test
        node_timeout: Duration::from_millis(500), // Reduce timeout
        ..Default::default()
    };
    let aggregator = DecentralizedStatsAggregator::new(config);

    // Simulate multiple nodes (these nodes don't exist in test environment, will cause connection failures)
    let node_infos = vec![
        NodeInfo {
            node_id: "node-1".to_string(),
            address: "127.0.0.1".to_string(),
            port: 9001,
            is_online: true,
            last_heartbeat: std::time::SystemTime::now(),
            version: "1.0.0".to_string(),
        },
        NodeInfo {
            node_id: "node-2".to_string(),
            address: "127.0.0.1".to_string(),
            port: 9002,
            is_online: true,
            last_heartbeat: std::time::SystemTime::now(),
            version: "1.0.0".to_string(),
        },
    ];

    // Add nodes to aggregator
    for node_info in node_infos {
        aggregator.add_node(node_info).await;
    }

    // Set local statistics (simulate local node)
    let local_stats = StatsSummary {
        node_id: "local-node".to_string(),
        total_objects_scanned: 1000,
        total_healthy_objects: 950,
        total_corrupted_objects: 50,
        total_bytes_scanned: 1024 * 1024 * 100, // 100MB
        total_scan_errors: 5,
        total_heal_triggered: 10,
        total_disks: 4,
        total_buckets: 5,
        last_update: std::time::SystemTime::now(),
        scan_progress: Default::default(),
        data_usage: rustfs_common::data_usage::DataUsageInfo::default(),
    };

    aggregator.set_local_stats(local_stats).await;

    // Get aggregated statistics (remote nodes will fail, but local node should succeed)
    let aggregated = aggregator.get_aggregated_stats().await.unwrap();

    // Verify local node statistics are included
    assert!(aggregated.node_summaries.contains_key("local-node"));
    assert!(aggregated.total_objects_scanned >= 1000);

    // Only local node data due to remote node connection failures
    assert_eq!(aggregated.node_summaries.len(), 1);

    // Test caching mechanism
    let original_timestamp = aggregated.aggregation_timestamp;

    let start_time = std::time::Instant::now();
    let cached_result = aggregator.get_aggregated_stats().await.unwrap();
    let cached_duration = start_time.elapsed();

    // Verify cache is effective: timestamps should be the same
    assert_eq!(original_timestamp, cached_result.aggregation_timestamp);

    // Cached calls should be fast (relaxed to 200ms for test environment)
    assert!(cached_duration < Duration::from_millis(200));

    // Force refresh
    let _refreshed = aggregator.force_refresh_aggregated_stats().await.unwrap();

    // Clear cache
    aggregator.clear_cache().await;

    // Verify cache status
    let cache_status = aggregator.get_cache_status().await;
    assert!(!cache_status.has_cached_data);
}

#[tokio::test]
async fn test_performance_impact_measurement() {
    let temp_dir = TempDir::new().unwrap();
    let scanner = create_test_scanner(&temp_dir).await;

    // Start performance monitoring
    let io_monitor = scanner.get_io_monitor();
    let _throttler = scanner.get_io_throttler();

    io_monitor.start().await.unwrap();

    // Baseline test: no scanner load - measure multiple times for stability
    const MEASUREMENT_COUNT: usize = 5;
    let mut baseline_measurements = Vec::new();
    for _ in 0..MEASUREMENT_COUNT {
        let duration = measure_workload(10_000, Duration::ZERO).await;
        baseline_measurements.push(duration);
    }
    // Use median to reduce impact of outliers
    baseline_measurements.sort();
    let median_idx = baseline_measurements.len() / 2;
    let baseline_duration = baseline_measurements[median_idx].max(Duration::from_millis(20));

    // Simulate scanner activity
    scanner.update_business_metrics(50, 500, 0, 25).await;

    tokio::time::sleep(Duration::from_millis(200)).await;

    // Performance test: with scanner load - measure multiple times for stability
    let mut scanner_measurements = Vec::new();
    for _ in 0..MEASUREMENT_COUNT {
        let duration = measure_workload(10_000, Duration::ZERO).await;
        scanner_measurements.push(duration);
    }
    scanner_measurements.sort();
    let median_idx = scanner_measurements.len() / 2;
    let with_scanner_duration = scanner_measurements[median_idx].max(baseline_duration);

    // Calculate performance impact
    let baseline_ns = baseline_duration.as_nanos().max(1) as f64;
    let overhead_duration = with_scanner_duration.saturating_sub(baseline_duration);
    let overhead_ns = overhead_duration.as_nanos() as f64;
    let overhead_ms = (overhead_ns / 1_000_000.0).round() as u64;
    let impact_percentage = (overhead_ns / baseline_ns) * 100.0;

    let benchmark = PerformanceBenchmark {
        _scanner_overhead_ms: overhead_ms,
        business_impact_percentage: impact_percentage,
        _throttle_effectiveness: 95.0, // Simulated value
    };

    println!("Performance impact measurement:");
    println!("  Baseline duration: {baseline_duration:?}");
    println!("  With scanner duration: {with_scanner_duration:?}");
    println!("  Overhead: {overhead_ms} ms");
    println!("  Impact percentage: {impact_percentage:.2}%");
    println!("  Meets optimization goals: {}", benchmark.meets_optimization_goals());

    // Verify optimization target (business impact < 50%)
    // Note: In test environment, allow higher threshold due to system load variability
    // In production, the actual impact should be much lower (< 10%)
    assert!(impact_percentage < 50.0, "Performance impact too high: {impact_percentage:.2}%");

    io_monitor.stop().await;
}

#[tokio::test]
async fn test_concurrent_scanner_operations() {
    let temp_dir = TempDir::new().unwrap();
    let scanner = Arc::new(create_test_scanner(&temp_dir).await);

    scanner.initialize_stats().await.unwrap();

    // Execute multiple scanner operations concurrently
    let tasks = vec![
        // Task 1: Periodically update business metrics
        {
            let scanner = scanner.clone();
            tokio::spawn(async move {
                for i in 0..10 {
                    scanner.update_business_metrics(10 + i * 5, 100 + i * 10, i, 5 + i).await;
                    tokio::time::sleep(Duration::from_millis(50)).await;
                }
            })
        },
        // Task 2: Periodically save checkpoints
        {
            let scanner = scanner.clone();
            tokio::spawn(async move {
                for _i in 0..5 {
                    if let Err(e) = scanner.force_save_checkpoint().await {
                        eprintln!("Checkpoint save failed: {e}");
                    }
                    tokio::time::sleep(Duration::from_millis(100)).await;
                }
            })
        },
        // Task 3: Periodically get statistics
        {
            let scanner = scanner.clone();
            tokio::spawn(async move {
                for _i in 0..8 {
                    let _summary = scanner.get_stats_summary().await;
                    let _progress = scanner.get_scan_progress().await;
                    tokio::time::sleep(Duration::from_millis(75)).await;
                }
            })
        },
    ];

    // Wait for all tasks to complete
    for task in tasks {
        task.await.unwrap();
    }

    // Verify final state
    let final_stats = scanner.get_stats_summary().await;
    let _final_progress = scanner.get_scan_progress().await;

    assert_eq!(final_stats.node_id, "integration-test-node");
    assert!(final_stats.last_update > std::time::SystemTime::UNIX_EPOCH);

    // Cleanup
    scanner.cleanup_checkpoint().await.unwrap();
}

// Helper function to simulate business workload
async fn simulate_business_workload(operations: usize) {
    for _i in 0..operations {
        // Simulate some CPU-intensive operations
        let _result: u64 = (0..100).map(|x| x * x).sum();

        // Small delay to simulate IO operations
        if _i % 100 == 0 {
            tokio::task::yield_now().await;
        }
    }
}

async fn measure_workload(operations: usize, extra_delay: Duration) -> Duration {
    let start = std::time::Instant::now();
    simulate_business_workload(operations).await;
    if !extra_delay.is_zero() {
        tokio::time::sleep(extra_delay).await;
    }
    start.elapsed()
}

#[tokio::test]
async fn test_error_recovery_and_resilience() {
    let temp_dir = TempDir::new().unwrap();
    let scanner = create_test_scanner(&temp_dir).await;

    // Test recovery from stats initialization failure
    scanner.initialize_stats().await.unwrap();

    // Test recovery from checkpoint corruption
    scanner.force_save_checkpoint().await.unwrap();

    // Artificially corrupt checkpoint file (by writing invalid data)
    let checkpoint_file = temp_dir.path().join("scanner_checkpoint_integration-test-node.json");
    if checkpoint_file.exists() {
        tokio::fs::write(&checkpoint_file, "invalid json data").await.unwrap();
    }

    // Verify system can gracefully handle corrupted checkpoint
    let checkpoint_info = scanner.get_checkpoint_info().await;
    // Should return error or null value, not crash
    assert!(checkpoint_info.is_err() || checkpoint_info.unwrap().is_none());

    // Clean up corrupted checkpoint
    scanner.cleanup_checkpoint().await.unwrap();

    // Verify ability to recreate valid checkpoint
    scanner.force_save_checkpoint().await.unwrap();
    let new_checkpoint_info = scanner.get_checkpoint_info().await.unwrap();
    assert!(new_checkpoint_info.is_some());
}
