//! Integration tests for error handling and retry logic
//!
//! These tests validate:
//! 1. Network failure handling and retries
//! 2. API error responses
//! 3. Data corruption detection
//! 4. Timeout handling
//! 5. Resource exhaustion handling
//!
//! TDD: These tests are written FIRST and should FAIL until implementation is complete.

#[path = "common/download_types.rs"]
mod download_types;
#[path = "common/mock_downloader.rs"]
mod mock_downloader;

use download_types::DownloadRequest;
use mock_downloader::{
    create_test_downloader_that_fails_midway, create_test_downloader_with_corrupted_data,
    create_test_downloader_with_error_type, create_test_downloader_with_invalid_auth,
    create_test_downloader_with_invalid_format, create_test_downloader_with_limited_disk,
    create_test_downloader_with_network_issues, create_test_downloader_with_rate_limiting,
    create_test_downloader_with_retry_tracking, create_test_downloader_with_timeout,
    create_test_service_with_concurrency_limit,
};
use std::time::Duration;
use tempfile::TempDir;

// Helper function to create test request
fn create_test_request() -> DownloadRequest {
    DownloadRequest::new_test_request()
}

/// Test: Network failure triggers retry with exponential backoff
#[tokio::test]
async fn test_network_failure_triggers_retry() {
    // Arrange: Downloader with simulated network failures
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader = create_test_downloader_with_network_issues(temp_dir.path()).await;

    let request = create_test_request();

    // Act: Attempt download (should retry)
    let result = downloader.download(request).await;

    // Assert: Eventually succeeds after retries
    assert!(
        result.is_ok(),
        "Should succeed after retries: {:?}",
        result.err()
    );

    let download_result = result.unwrap();
    assert!(
        download_result.retry_count > 0,
        "Should have retried at least once"
    );
    assert!(
        download_result.retry_count <= 3,
        "Should not exceed max retries"
    );
}

/// Test: Exponential backoff timing
#[tokio::test]
async fn test_exponential_backoff_timing() {
    // Arrange: Downloader that tracks retry timings
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader = create_test_downloader_with_retry_tracking(temp_dir.path()).await;

    let request = create_test_request();

    // Act: Trigger retries
    let result = downloader.download(request).await;

    // Assert: Retry delays follow exponential backoff
    assert!(result.is_ok());

    let retry_delays = downloader.get_retry_delays();
    assert_eq!(retry_delays.len(), 2, "Should have 2 retries");

    // First retry: ~1s delay
    assert!(
        retry_delays[0] >= Duration::from_millis(900),
        "First retry should wait ~1s"
    );
    assert!(
        retry_delays[0] <= Duration::from_millis(1500),
        "First retry should not wait too long"
    );

    // Second retry: ~2s delay
    assert!(
        retry_delays[1] >= Duration::from_millis(1800),
        "Second retry should wait ~2s"
    );
    assert!(
        retry_delays[1] <= Duration::from_millis(3000),
        "Second retry should not wait too long"
    );
}

/// Test: API rate limit error triggers appropriate backoff
#[tokio::test]
async fn test_rate_limit_error_triggers_backoff() {
    // Arrange: Downloader that simulates rate limiting
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader = create_test_downloader_with_rate_limiting(temp_dir.path()).await;

    let request = create_test_request();

    // Act: Download (will hit rate limit)
    let result = downloader.download(request).await;

    // Assert: Handles rate limiting gracefully
    assert!(result.is_ok(), "Should handle rate limiting");

    let download_result = result.unwrap();
    assert!(
        download_result.was_rate_limited,
        "Should detect rate limiting"
    );
    assert!(
        download_result.total_wait_time >= Duration::from_secs(5),
        "Should wait for rate limit cooldown"
    );
}

/// Test: API authentication failure is not retried
#[tokio::test]
async fn test_authentication_failure_not_retried() {
    // Arrange: Downloader with invalid credentials
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader = create_test_downloader_with_invalid_auth(temp_dir.path()).await;

    let request = create_test_request();

    // Act: Attempt download
    let result = downloader.download(request).await;

    // Assert: Fails immediately without retry
    assert!(result.is_err(), "Should fail with auth error");

    let error = result.unwrap_err();
    let error_msg = error.to_string().to_lowercase();
    assert!(
        error_msg.contains("authentication") || error_msg.contains("unauthorized"),
        "Error should indicate auth failure: {}",
        error
    );

    // Verify no retries were attempted
    assert_eq!(
        downloader.get_retry_count(),
        0,
        "Should not retry auth failures"
    );
}

/// Test: Timeout during download is handled
#[tokio::test]
async fn test_download_timeout_handled() {
    // Arrange: Downloader with short timeout
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader =
        create_test_downloader_with_timeout(temp_dir.path(), Duration::from_millis(100)).await;

    let request = create_test_request();

    // Act: Download (will timeout)
    let result = downloader.download(request).await;

    // Assert: Timeout error is raised
    assert!(result.is_err(), "Should fail with timeout");

    let error = result.unwrap_err();
    assert!(
        error.to_string().contains("timeout"),
        "Error should indicate timeout: {}",
        error
    );
}

/// Test: Data corruption is detected and download fails
#[tokio::test]
async fn test_data_corruption_detected() {
    // Arrange: Downloader that returns corrupted data
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader = create_test_downloader_with_corrupted_data(temp_dir.path()).await;

    let request = create_test_request();

    // Act: Download
    let result = downloader.download(request).await;

    // Assert: Corruption is detected
    assert!(result.is_err(), "Should fail with corruption error");

    let error = result.unwrap_err();
    assert!(
        error.to_string().contains("checksum")
            || error.to_string().contains("corruption")
            || error.to_string().contains("integrity"),
        "Error should indicate data corruption: {}",
        error
    );
}

/// Test: Invalid response format is handled
#[tokio::test]
async fn test_invalid_response_format_handled() {
    // Arrange: Downloader that returns invalid format
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader = create_test_downloader_with_invalid_format(temp_dir.path()).await;

    let request = create_test_request();

    // Act: Download
    let result = downloader.download(request).await;

    // Assert: Invalid format error
    assert!(result.is_err(), "Should fail with format error");

    let error = result.unwrap_err();
    assert!(
        error.to_string().contains("format")
            || error.to_string().contains("parse")
            || error.to_string().contains("invalid"),
        "Error should indicate format issue: {}",
        error
    );
}

/// Test: Disk space exhaustion is detected
#[tokio::test]
async fn test_disk_space_exhaustion_detected() {
    // Arrange: Downloader with insufficient disk space
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader = create_test_downloader_with_limited_disk(temp_dir.path()).await;

    let request = create_test_request();

    // Act: Download
    let result = downloader.download(request).await;

    // Assert: Disk space error
    assert!(result.is_err(), "Should fail with disk space error");

    let error = result.unwrap_err();
    assert!(
        error.to_string().contains("disk")
            || error.to_string().contains("space")
            || error.to_string().contains("storage"),
        "Error should indicate disk space issue: {}",
        error
    );
}

/// Test: Partial download is cleaned up on failure
#[tokio::test]
async fn test_partial_download_cleaned_up() {
    // Arrange: Downloader that fails mid-download
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let downloader = create_test_downloader_that_fails_midway(temp_dir.path()).await;

    let request = create_test_request();

    // Get initial file count
    let initial_files = std::fs::read_dir(temp_dir.path())
        .expect("Failed to read dir")
        .count();

    // Act: Download (will fail)
    let result = downloader.download(request).await;

    // Assert: Partial files are cleaned up
    assert!(result.is_err(), "Should fail");

    let final_files = std::fs::read_dir(temp_dir.path())
        .expect("Failed to read dir")
        .count();

    assert_eq!(
        initial_files, final_files,
        "Partial files should be cleaned up"
    );
}

/// Test: Concurrent download limits are enforced
#[tokio::test]
async fn test_concurrent_download_limits_enforced() {
    // Arrange: Service with max 2 concurrent downloads
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let service = create_test_service_with_concurrency_limit(temp_dir.path(), 2).await;

    // Act: Schedule 5 downloads
    let mut job_ids = vec![];
    for i in 0..5 {
        let mut request = create_test_request();
        request.description = format!("Download {}", i);

        let response = service
            .schedule_download(request)
            .await
            .expect("Schedule failed");
        job_ids.push(response.job_id);
    }

    // Wait briefly for downloads to start
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Assert: Only 2 are downloading, rest are pending
    let mut downloading_count = 0;
    let mut pending_count = 0;

    for job_id in job_ids {
        let status = service
            .get_download_status(job_id)
            .await
            .expect("Get status failed");

        match status.job_details.status {
            2 => downloading_count += 1, // DOWNLOADING
            1 => pending_count += 1,     // PENDING
            _ => {},
        }
    }

    assert_eq!(
        downloading_count, 2,
        "Should have exactly 2 concurrent downloads"
    );
    assert!(pending_count >= 3, "Remaining should be pending");
}

/// Test: Error messages are descriptive
#[tokio::test]
async fn test_error_messages_are_descriptive() {
    // Arrange: Various error scenarios
    let temp_dir = TempDir::new().expect("Failed to create temp dir");

    let test_cases = vec![
        ("network", "Failed to connect to Databento API"),
        ("auth", "Authentication failed: invalid API key"),
        ("rate_limit", "Rate limit exceeded: retry after"),
        ("not_found", "Symbol not found in dataset"),
        (
            "invalid_date",
            "Invalid date range: start_date must be before end_date",
        ),
    ];

    for (error_type, expected_message_fragment) in test_cases {
        let downloader = create_test_downloader_with_error_type(temp_dir.path(), error_type).await;
        let request = create_test_request();

        // Act: Trigger error
        let result = downloader.download(request).await;

        // Assert: Error message is descriptive
        assert!(
            result.is_err(),
            "Should fail for error type: {}",
            error_type
        );

        let error = result.unwrap_err();
        assert!(
            error.to_string().contains(expected_message_fragment),
            "Error for {} should contain '{}', got: {}",
            error_type,
            expected_message_fragment,
            error
        );
    }
}
