//! Tests for the CircuitBreakerStore implementation.

use super::*;
use crate::HybridStore;

async fn create_store() -> GenericCircuitBreakerStore<HybridStore> {
    let kv_store = Arc::new(HybridStore::open_temp().expect("store"));
    GenericCircuitBreakerStore::with_defaults(kv_store)
}

#[tokio::test]
async fn test_new_agent_allowed() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    assert!(store.check_allowed(&agent_id, 1000).await.expect("check"));
    assert!(store.get_circuit(&agent_id).await.expect("get").is_none());
}

#[tokio::test]
async fn test_failures_trip_circuit() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    // Record 4 failures (below threshold of 5)
    for i in 0..4 {
        let record = store
            .record_failure(&agent_id, FailureType::InvalidSignature, 1000 + i)
            .await
            .expect("record");
        assert_eq!(record.state, CircuitState::Closed);
    }

    // 5th failure trips the circuit
    let record =
        store.record_failure(&agent_id, FailureType::InvalidSignature, 1004).await.expect("record");

    assert_eq!(record.state, CircuitState::Open);
    assert_eq!(record.trip_count, 1);

    // Agent should be blocked
    assert!(!store.check_allowed(&agent_id, 1005).await.expect("check"));
}

#[tokio::test]
async fn test_open_transitions_to_half_open_after_timeout() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    // Trip the circuit
    for i in 0..5 {
        store
            .record_failure(&agent_id, FailureType::InvalidSignature, 1000 + i)
            .await
            .expect("record");
    }

    // Still blocked before timeout
    assert!(!store.check_allowed(&agent_id, 1010).await.expect("check"));

    // After timeout (30 seconds), should transition to HalfOpen and be allowed
    assert!(store.check_allowed(&agent_id, 1035).await.expect("check"));

    let record = store.get_circuit(&agent_id).await.expect("get").expect("record");
    assert_eq!(record.state, CircuitState::HalfOpen);
}

#[tokio::test]
async fn test_half_open_success_closes_circuit() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    // Trip the circuit
    for i in 0..5 {
        store
            .record_failure(&agent_id, FailureType::InvalidSignature, 1000 + i)
            .await
            .expect("record");
    }

    // Wait for timeout and transition to HalfOpen
    store.check_allowed(&agent_id, 1035).await.expect("check");

    // Record success - should close the circuit
    store.record_success(&agent_id, 1036).await.expect("success");

    // Record should be deleted (circuit closed and cleaned up)
    assert!(store.get_circuit(&agent_id).await.expect("get").is_none());

    // Agent should be fully allowed
    assert!(store.check_allowed(&agent_id, 1040).await.expect("check"));
}

#[tokio::test]
async fn test_half_open_failure_re_trips() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    // Trip the circuit
    for i in 0..5 {
        store
            .record_failure(&agent_id, FailureType::InvalidSignature, 1000 + i)
            .await
            .expect("record");
    }

    // Wait for timeout and transition to HalfOpen
    store.check_allowed(&agent_id, 1035).await.expect("check");

    // Record failure in HalfOpen - should re-trip
    let record =
        store.record_failure(&agent_id, FailureType::InvalidSignature, 1036).await.expect("record");

    assert_eq!(record.state, CircuitState::Open);
    assert_eq!(record.trip_count, 2);

    // Agent should be blocked again
    assert!(!store.check_allowed(&agent_id, 1040).await.expect("check"));
}

#[tokio::test]
async fn test_reset_circuit() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    // Trip the circuit
    for i in 0..5 {
        store
            .record_failure(&agent_id, FailureType::InvalidSignature, 1000 + i)
            .await
            .expect("record");
    }

    assert!(!store.check_allowed(&agent_id, 1010).await.expect("check"));

    // Admin reset
    store.reset_circuit(&agent_id).await.expect("reset");

    // Agent should be allowed
    assert!(store.check_allowed(&agent_id, 1015).await.expect("check"));
    assert!(store.get_circuit(&agent_id).await.expect("get").is_none());
}

#[tokio::test]
async fn test_list_tripped() {
    let store = create_store().await;

    // Trip 3 circuits at different times
    for i in 1..=3 {
        let agent_id = [i; 32];
        for j in 0..5 {
            store
                .record_failure(&agent_id, FailureType::InvalidSignature, (i as u64) * 1000 + j)
                .await
                .expect("record");
        }
    }

    let tripped = store.list_tripped(10).await.expect("list");
    assert_eq!(tripped.len(), 3);

    // Should be ordered by last failure time (most recent first)
    assert_eq!(tripped[0].agent_id, [3u8; 32]);
    assert_eq!(tripped[1].agent_id, [2u8; 32]);
    assert_eq!(tripped[2].agent_id, [1u8; 32]);
}

#[tokio::test]
async fn test_list_tripped_excludes_closed() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    // Trip and then reset
    for i in 0..5 {
        store
            .record_failure(&agent_id, FailureType::InvalidSignature, 1000 + i)
            .await
            .expect("record");
    }
    store.reset_circuit(&agent_id).await.expect("reset");

    // Trip another agent
    let agent_id2 = [2u8; 32];
    for i in 0..5 {
        store
            .record_failure(&agent_id2, FailureType::InvalidSignature, 2000 + i)
            .await
            .expect("record");
    }

    let tripped = store.list_tripped(10).await.expect("list");
    assert_eq!(tripped.len(), 1);
    assert_eq!(tripped[0].agent_id, agent_id2);
}

#[tokio::test]
async fn test_retry_after() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    // No record - no retry_after
    assert!(store.retry_after(&agent_id, 1000).await.expect("retry").is_none());

    // Trip the circuit at t=1000
    for _ in 0..5 {
        store.record_failure(&agent_id, FailureType::InvalidSignature, 1000).await.expect("record");
    }

    // Check retry_after at t=1010 (20 seconds remaining)
    let retry = store.retry_after(&agent_id, 1010).await.expect("retry");
    assert_eq!(retry, Some(20));

    // At timeout (t=1030), should be 0
    let retry = store.retry_after(&agent_id, 1030).await.expect("retry");
    assert_eq!(retry, Some(0));
}

#[tokio::test]
async fn test_failures_outside_window_not_counted() {
    // Use custom config with 10 second window
    let kv_store = Arc::new(HybridStore::open_temp().expect("store"));
    let config = CircuitBreakerConfig::new(5, 30, 10, 1);
    let store = GenericCircuitBreakerStore::new(kv_store, config);

    let agent_id = [1u8; 32];

    // Record 3 failures at t=1000
    for _ in 0..3 {
        store.record_failure(&agent_id, FailureType::InvalidSignature, 1000).await.expect("record");
    }

    // Record 2 more failures at t=1015 (outside 10 second window)
    // Only these 2 should count, old ones should be pruned
    for _ in 0..2 {
        let record = store
            .record_failure(&agent_id, FailureType::InvalidSignature, 1015)
            .await
            .expect("record");
        assert_eq!(record.state, CircuitState::Closed); // Should not trip yet
    }

    // Verify only 2 failures in the window
    let record = store.get_circuit(&agent_id).await.expect("get").expect("record");
    assert_eq!(record.failure_count(), 2);
}

#[tokio::test]
async fn test_different_failure_types() {
    let store = create_store().await;
    let agent_id = [1u8; 32];

    // Record different failure types
    store.record_failure(&agent_id, FailureType::InvalidSignature, 1000).await.expect("record");
    store.record_failure(&agent_id, FailureType::InputValidation, 1001).await.expect("record");
    store.record_failure(&agent_id, FailureType::PowError, 1002).await.expect("record");
    store.record_failure(&agent_id, FailureType::QuotaExceeded, 1003).await.expect("record");
    let record =
        store.record_failure(&agent_id, FailureType::ApplicationError, 1004).await.expect("record");

    // All types count toward threshold
    assert_eq!(record.state, CircuitState::Open);

    // Verify counts by type
    assert_eq!(record.count_failures_by_type(FailureType::InvalidSignature), 1);
    assert_eq!(record.count_failures_by_type(FailureType::InputValidation), 1);
    assert_eq!(record.count_failures_by_type(FailureType::PowError), 1);
    assert_eq!(record.count_failures_by_type(FailureType::QuotaExceeded), 1);
    assert_eq!(record.count_failures_by_type(FailureType::ApplicationError), 1);
}
