#![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,
    failure: FailureMode,
) -> Arc<ArchiveRegistration> {
    Arc::new(ArchiveRegistration::new(
        name.into(),
        "mock",
        backend as Arc<dyn ArchiveBackend>,
        true,
        failure,
        priority,
        true,
        WritePolicy::Inline,
        None,
        HealthStatus::Healthy,
    ))
}

#[tokio::test]
async fn five_consecutive_failures_drifts_to_unavailable() {
    let primary = Arc::new(MockBackend::new());
    let secondary = Arc::new(MockBackend::new());
    secondary.inject_write_failures(10);

    let archivist = Archivist::from_registrations(vec![
        reg("primary", primary.clone(), 0, FailureMode::Required),
        reg("secondary", secondary.clone(), 10, FailureMode::BestEffort),
    ]);

    for _ in 0..5 {
        archivist
            .append_messages(Uuid::new_v4(), vec![], None)
            .await
            .ok();
    }

    let snapshot = archivist.list_archives_with_health().await;
    let secondary_status = snapshot.iter().find(|s| s.name == "secondary").unwrap();
    assert!(
        matches!(secondary_status.health, HealthStatus::Unavailable { .. }),
        "secondary should be Unavailable after 5 failures; got {:?}",
        secondary_status.health
    );
}

#[tokio::test]
async fn success_after_failure_recovers_to_healthy() {
    let backend = Arc::new(MockBackend::new());
    backend.inject_write_failures(1);

    let archivist = Archivist::from_registrations(vec![reg(
        "only",
        backend.clone(),
        0,
        FailureMode::Required,
    )]);

    // First call fails.
    let _ = archivist
        .append_messages(Uuid::new_v4(), vec![], None)
        .await;
    let snapshot = archivist.list_archives_with_health().await;
    assert!(
        matches!(snapshot[0].health, HealthStatus::Degraded { .. }),
        "expected Degraded after first failure; got {:?}",
        snapshot[0].health
    );

    // Second call succeeds — health returns to Healthy.
    archivist
        .append_messages(Uuid::new_v4(), vec![], None)
        .await
        .unwrap();
    let snapshot = archivist.list_archives_with_health().await;
    assert!(
        matches!(snapshot[0].health, HealthStatus::Healthy),
        "expected Healthy after recovery; got {:?}",
        snapshot[0].health
    );
}

#[tokio::test]
async fn unavailable_backend_skipped_during_read_walk() {
    let primary = Arc::new(MockBackend::new());
    let secondary = Arc::new(MockBackend::new());

    let scroll = Uuid::new_v4();
    secondary
        .put_session(SessionMetadata::stub(scroll))
        .await
        .unwrap();
    secondary.break_permanently("kaput");

    let primary_reg = reg("primary", primary.clone(), 0, FailureMode::Required);
    let secondary_reg = reg("secondary", secondary.clone(), 10, FailureMode::Required);
    // Force secondary's cached health to Unavailable BEFORE the walk,
    // so the routing layer skips it entirely rather than attempting + failing.
    *secondary_reg.last_health.write().await = HealthStatus::Unavailable {
        reason: "test".into(),
    };

    let archivist = Archivist::from_registrations(vec![primary_reg, secondary_reg]);

    // Primary doesn't have the session; secondary has it but is marked Unavailable.
    // Read walk should skip secondary → Ok(None)-style ergonomics, bubbling up
    // as `SessionUnknown` per `get_session_metadata`'s contract.
    let result = archivist.get_session_metadata(scroll, None).await;
    assert!(
        result.is_err(),
        "expected SessionUnknown error when Unavailable backend is skipped"
    );
    assert!(matches!(
        result.unwrap_err(),
        dirigent_archivist::error::ArchivistError::SessionUnknown(_)
    ));
}
