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

use std::sync::Arc;

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

async fn dual_backend_coordinator() -> (Archivist, Arc<MockBackend>, Arc<MockBackend>) {
    let a = Arc::new(MockBackend::new());
    let b = Arc::new(MockBackend::new());
    let regs = vec![
        Arc::new(ArchiveRegistration::new(
            "a".into(),
            "mock",
            a.clone() as Arc<dyn ArchiveBackend>,
            true,
            FailureMode::Required,
            0,
            true,
            WritePolicy::Inline,
            None,
            HealthStatus::Healthy,
        )),
        Arc::new(ArchiveRegistration::new(
            "b".into(),
            "mock",
            b.clone() as Arc<dyn ArchiveBackend>,
            true,
            FailureMode::Required,
            10,
            true,
            WritePolicy::Inline,
            None,
            HealthStatus::Healthy,
        )),
    ];
    (Archivist::from_registrations(regs), a, b)
}

#[tokio::test]
async fn copy_session_carries_metadata_and_messages() {
    let (archivist, a, b) = dual_backend_coordinator().await;
    let scroll = Uuid::new_v4();

    // Seed `a` only.
    a.put_session(SessionMetadata::stub(scroll)).await.unwrap();
    a.append_messages(scroll, vec![]).await.unwrap();

    archivist.copy_session(scroll, "a", "b").await.unwrap();

    assert!(b.get_session(scroll).await.unwrap().is_some());
    assert!(a.get_session(scroll).await.unwrap().is_some());
}

#[tokio::test]
async fn move_session_removes_from_source() {
    let (archivist, a, b) = dual_backend_coordinator().await;
    let scroll = Uuid::new_v4();
    a.put_session(SessionMetadata::stub(scroll)).await.unwrap();

    archivist.move_session(scroll, "a", "b").await.unwrap();

    assert!(a.get_session(scroll).await.unwrap().is_none());
    assert!(b.get_session(scroll).await.unwrap().is_some());
    assert_eq!(
        archivist.read_cache_size().await,
        1,
        "cache should now reflect the move"
    );
}

#[tokio::test]
async fn move_session_partial_failure_returns_partial_move_error() {
    let (archivist, a, b) = dual_backend_coordinator().await;
    let scroll = Uuid::new_v4();
    a.put_session(SessionMetadata::stub(scroll)).await.unwrap();

    // The source-side delete happens AFTER the copy. Inject ONE write failure
    // AFTER the copy has already consumed the write capacity. `MockBackend`'s
    // inject_write_failures decrements on every mutating call — so we:
    // 1. perform the copy through the archivist (uses put_session+append on `b`,
    //    but NO writes on `a`, since reads happen on the source side).
    // 2. THEN inject a write failure on `a` to make the delete fail.
    //
    // Actually `copy_session` reads from `a` then writes to `b`, no writes on `a`.
    // So we can safely inject BEFORE calling move_session: the only write on `a`
    // during move_session is the delete, which will hit the injected failure.

    a.inject_write_failures(1);

    let err = archivist.move_session(scroll, "a", "b").await.unwrap_err();
    assert!(matches!(err, ArchivistError::PartialMove { .. }));

    // Both backends now have the session.
    assert!(a.get_session(scroll).await.unwrap().is_some());
    assert!(b.get_session(scroll).await.unwrap().is_some());
}

#[tokio::test]
async fn delete_session_fans_out_and_invalidates_cache() {
    let (archivist, a, b) = dual_backend_coordinator().await;
    let scroll = Uuid::new_v4();
    a.put_session(SessionMetadata::stub(scroll)).await.unwrap();
    b.put_session(SessionMetadata::stub(scroll)).await.unwrap();

    // Prime the cache with a read.
    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!(a.get_session(scroll).await.unwrap().is_none());
    assert!(b.get_session(scroll).await.unwrap().is_none());
    assert_eq!(archivist.read_cache_size().await, 0);
}
