#![cfg(feature = "test-utils")]

use std::sync::Arc;

use dirigent_archivist::backend::mock::MockBackend;
use dirigent_archivist::backend::{ArchiveBackend, HealthStatus};
use dirigent_archivist::coordinator::Archivist;
use dirigent_archivist::registry::{ArchiveRegistration, FailureMode, WritePolicy};
use dirigent_archivist::types::SessionMetadata;
use uuid::Uuid;

fn reg(name: &str, backend: Arc<MockBackend>, priority: u32) -> Arc<ArchiveRegistration> {
    Arc::new(ArchiveRegistration::new(
        name.into(),
        "mock",
        backend as Arc<dyn ArchiveBackend>,
        true,
        FailureMode::Required,
        priority,
        true,
        WritePolicy::Inline,
        None,
        HealthStatus::Healthy,
    ))
}

#[tokio::test]
async fn high_priority_backend_serves_first() {
    let high = Arc::new(MockBackend::new());
    let low = Arc::new(MockBackend::new());
    let scroll = Uuid::new_v4();
    high.put_session(SessionMetadata::stub(scroll)).await.unwrap();

    let archivist = Archivist::from_registrations(vec![
        reg("high", high.clone(), 0),
        reg("low", low.clone(), 10),
    ]);

    let meta = archivist.get_session_metadata(scroll, None).await;
    assert!(meta.is_ok(), "expected Ok; got {:?}", meta);
    assert_eq!(archivist.read_cache_size().await, 1);
}

#[tokio::test]
async fn falls_through_to_lower_priority_when_high_misses() {
    let high = Arc::new(MockBackend::new());
    let low = Arc::new(MockBackend::new());
    let scroll = Uuid::new_v4();
    low.put_session(SessionMetadata::stub(scroll)).await.unwrap();

    let archivist = Archivist::from_registrations(vec![
        reg("high", high.clone(), 0),
        reg("low", low.clone(), 10),
    ]);

    let meta = archivist.get_session_metadata(scroll, None).await;
    assert!(meta.is_ok(), "expected Ok; got {:?}", meta);
    assert_eq!(archivist.read_cache_size().await, 1);
}

#[tokio::test]
async fn cache_makes_second_read_skip_priority_walk() {
    let high = Arc::new(MockBackend::new());
    let low = Arc::new(MockBackend::new());
    let scroll = Uuid::new_v4();
    low.put_session(SessionMetadata::stub(scroll)).await.unwrap();

    let archivist = Archivist::from_registrations(vec![
        reg("high", high.clone(), 0),
        reg("low", low.clone(), 10),
    ]);

    // Prime the cache.
    let _ = archivist.get_session_metadata(scroll, None).await.unwrap();

    // Inject a read failure on `high` to detect whether the second read walks it.
    // If the cache works, `high` must NOT be touched.
    high.inject_read_failures(1);
    let _ = archivist.get_session_metadata(scroll, None).await.unwrap();

    let snapshot = archivist.list_archives_with_health().await;
    let high_status = snapshot.iter().find(|s| s.name == "high").unwrap();
    assert!(
        matches!(high_status.health, HealthStatus::Healthy),
        "cache should have skipped `high`; got {:?}",
        high_status.health
    );
}

#[tokio::test]
async fn delete_invalidates_cache() {
    let high = Arc::new(MockBackend::new());
    let scroll = Uuid::new_v4();
    high.put_session(SessionMetadata::stub(scroll)).await.unwrap();

    let archivist = Archivist::from_registrations(vec![reg("high", high.clone(), 0)]);
    let _ = archivist.get_session_metadata(scroll, None).await.unwrap();
    assert_eq!(archivist.read_cache_size().await, 1);

    archivist.delete_session(scroll, None).await.unwrap();
    assert_eq!(archivist.read_cache_size().await, 0);
}
