#![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 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,
    ))
}

fn sample_message(session: Uuid) -> dirigent_archivist::types::MessageRecord {
    dirigent_archivist::types::MessageRecord {
        version: 1,
        message_id: Uuid::now_v7(),
        session,
        parent_id: None,
        ts: chrono::Utc::now(),
        role: "user".into(),
        author: None,
        content_md: "hi".into(),
        content_parts: None,
        attachments: vec![],
        metadata: serde_json::Value::Null,
    }
}

#[tokio::test]
async fn write_fans_out_to_both_backends() {
    let a = Arc::new(MockBackend::new());
    let b = Arc::new(MockBackend::new());
    let archivist = Archivist::from_registrations(vec![
        reg("a", a.clone(), 0, FailureMode::Required),
        reg("b", b.clone(), 10, FailureMode::BestEffort),
    ]);

    // Using a non-empty message vec for a robust positive-count check:
    let scroll = Uuid::new_v4();
    let m = sample_message(scroll);
    archivist
        .append_messages(scroll, vec![m], None)
        .await
        .unwrap();
    assert_eq!(a.appended_count(scroll), 1);
    assert_eq!(b.appended_count(scroll), 1);
}

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

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

    archivist
        .append_messages(Uuid::new_v4(), vec![], None)
        .await
        .unwrap(); // Ok despite secondary failure

    let snapshot = archivist.list_archives_with_health().await;
    let b_status = snapshot.iter().find(|s| s.name == "b").unwrap();
    assert!(matches!(b_status.health, HealthStatus::Degraded { .. }));
}

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

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

    let err = archivist
        .append_messages(Uuid::new_v4(), vec![], None)
        .await;
    assert!(err.is_err(), "expected error when required secondary fails");
}

#[tokio::test]
async fn explicit_archive_overrides_default_primary() {
    let a = Arc::new(MockBackend::new());
    let b = Arc::new(MockBackend::new());
    let archivist = Archivist::from_registrations(vec![
        reg("a", a.clone(), 0, FailureMode::Required),
        reg("b", b.clone(), 10, FailureMode::Required),
    ]);

    let scroll = Uuid::new_v4();
    let m = sample_message(scroll);
    archivist
        .append_messages(scroll, vec![m], Some("b".into()))
        .await
        .unwrap();

    // Both receive the write: b is explicit primary, a is secondary via fanout.
    assert_eq!(a.appended_count(scroll), 1);
    assert_eq!(b.appended_count(scroll), 1);
}
