//! Integration test for timeout handling (T049)
//!
//! This test verifies that the system handles timeouts gracefully when a client
//! fails to respond to an agent request within the timeout period.
//!
//! Test scenario:
//! 1. Register a pending agent request
//! 2. Wait for timeout (using reduced timeout for testing)
//! 3. Verify timeout occurs
//! 4. Verify cleanup happens correctly
//! 5. Verify no resource leaks

use dirigent_acp_api::agent_requests::AgentRequestTracker;
use serde_json::json;
use tokio::time::{timeout, Duration};

#[tokio::test]
async fn test_timeout_basic() {
    // Create tracker
    let tracker = AgentRequestTracker::new();

    let client_id = "test-client";
    let request_id = json!(0);

    // Register request
    let receiver = tracker.register(client_id, request_id.clone());
    assert_eq!(tracker.pending_count(), 1);

    // Wait for timeout (use short timeout for testing)
    let result = timeout(Duration::from_millis(100), receiver).await;

    // Should timeout
    assert!(result.is_err(), "Expected timeout but request completed");

    // Manually trigger cleanup (in production, event bridge does this)
    tracker.timeout(client_id, request_id);

    // Verify cleanup
    assert_eq!(tracker.pending_count(), 0);
}

#[tokio::test]
async fn test_timeout_cleanup() {
    let tracker = AgentRequestTracker::new();

    let client_id = "test-client";
    let request_id = json!(123);

    // Register request
    let receiver = tracker.register(client_id, request_id.clone());
    assert_eq!(tracker.pending_count(), 1);

    // Trigger timeout before waiting
    tracker.timeout(client_id, request_id);

    // Verify cleanup happened
    assert_eq!(tracker.pending_count(), 0);

    // Receiver should get error (channel closed)
    let result = receiver.await;
    assert!(result.is_err(), "Expected receiver to get error after timeout");
}

#[tokio::test]
async fn test_timeout_multiple_clients() {
    let tracker = AgentRequestTracker::new();

    let client1 = "client-1";
    let client2 = "client-2";

    // Register requests for both clients
    let rx1 = tracker.register(client1, json!(0));
    let rx2 = tracker.register(client2, json!(0));

    assert_eq!(tracker.pending_count(), 2);
    assert_eq!(tracker.client_pending_count(client1), 1);
    assert_eq!(tracker.client_pending_count(client2), 1);

    // Timeout only client1's request
    tracker.timeout(client1, json!(0));

    // Verify only client1's request is removed
    assert_eq!(tracker.pending_count(), 1);
    assert_eq!(tracker.client_pending_count(client1), 0);
    assert_eq!(tracker.client_pending_count(client2), 1);

    // Client1's receiver should error
    assert!(rx1.await.is_err());

    // Complete client2's request normally
    let response = json!({"result": "success"});
    let result = tracker.complete(client2, json!(0), response);
    assert!(result.is_ok());

    // Client2's receiver should get response
    let received = rx2.await.unwrap();
    assert_eq!(received, json!({"result": "success"}));

    // All cleaned up
    assert_eq!(tracker.pending_count(), 0);
}

#[tokio::test]
async fn test_timeout_no_double_cleanup() {
    let tracker = AgentRequestTracker::new();

    let client_id = "test-client";
    let request_id = json!(0);

    // Register request
    let _receiver = tracker.register(client_id, request_id.clone());
    assert_eq!(tracker.pending_count(), 1);

    // First timeout - should remove
    tracker.timeout(client_id, request_id.clone());
    assert_eq!(tracker.pending_count(), 0);

    // Second timeout - should be no-op (not panic)
    tracker.timeout(client_id, request_id);
    assert_eq!(tracker.pending_count(), 0);
}

#[tokio::test]
async fn test_timeout_race_with_complete() {
    let tracker = AgentRequestTracker::new();

    let client_id = "test-client";
    let request_id = json!(0);

    // Register request
    let receiver = tracker.register(client_id, request_id.clone());
    assert_eq!(tracker.pending_count(), 1);

    // Complete the request
    let response = json!({"result": "success"});
    let result = tracker.complete(client_id, request_id.clone(), response.clone());
    assert!(result.is_ok());
    assert_eq!(tracker.pending_count(), 0);

    // Try to timeout after completion - should be no-op
    tracker.timeout(client_id, request_id);
    assert_eq!(tracker.pending_count(), 0);

    // Receiver should still get the response
    let received = receiver.await.unwrap();
    assert_eq!(received, response);
}

#[tokio::test]
async fn test_concurrent_timeouts() {
    let tracker = AgentRequestTracker::new();

    let client_id = "test-client";

    // Register 10 requests
    let mut receivers = Vec::new();
    for i in 0..10 {
        let rx = tracker.register(client_id, json!(i));
        receivers.push((i, rx));
    }

    assert_eq!(tracker.pending_count(), 10);

    // Spawn tasks to timeout each request after random delays
    let tracker_clone = tracker.clone();
    let timeout_handles: Vec<_> = (0..10)
        .map(|i| {
            let tracker = tracker_clone.clone();
            tokio::spawn(async move {
                // Small random-ish delay based on index
                tokio::time::sleep(Duration::from_millis((i * 10) as u64)).await;
                tracker.timeout(client_id, json!(i));
            })
        })
        .collect();

    // Wait for all timeouts to complete
    for handle in timeout_handles {
        handle.await.unwrap();
    }

    // All should be cleaned up
    assert_eq!(tracker.pending_count(), 0);

    // All receivers should get errors
    for (_i, rx) in receivers {
        assert!(rx.await.is_err());
    }
}

#[tokio::test]
async fn test_timeout_with_actual_delay() {
    // This test uses actual time delays to verify timeout behavior more realistically
    let tracker = AgentRequestTracker::new();

    let client_id = "test-client";
    let request_id = json!(0);

    let start = std::time::Instant::now();

    // Register request
    let receiver = tracker.register(client_id, request_id.clone());

    // Spawn task to timeout after 200ms
    let tracker_clone = tracker.clone();
    tokio::spawn(async move {
        tokio::time::sleep(Duration::from_millis(200)).await;
        tracker_clone.timeout(client_id, json!(0));
    });

    // Wait on receiver with longer timeout
    let result = timeout(Duration::from_secs(1), receiver).await;

    let elapsed = start.elapsed();

    // Should complete due to timeout() call, not tokio::time::timeout
    assert!(result.is_ok(), "Should complete when timeout() is called");
    assert!(result.unwrap().is_err(), "Receiver should get error");

    // Should take approximately 200ms
    assert!(
        elapsed >= Duration::from_millis(180) && elapsed < Duration::from_millis(300),
        "Expected ~200ms but took {:?}",
        elapsed
    );

    // Should be cleaned up
    assert_eq!(tracker.pending_count(), 0);
}
