//! Typed, actionable errors (S2).
//!
//! Joining a Welcome whose KeyPackage has already been consumed (single-use
//! reuse / pool exhaustion) must surface the typed `Error::KeyPackageNotFound`,
//! NOT an opaque `Error::Mls`, so the host can react (top up KeyPackages).

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

use std::sync::Arc;

use futures::executor::block_on;
use parking_lot::Mutex;

use ping_core::{
    storage::memory::MemStorage, transport::DiscoveredDevice, transport::TransportSubscription,
    ClientConfig, ConversationId, Error, Identity, MessageEnvelope, MessageKind, MessagingClient,
    Storage, SyncCursor, Transport,
};

#[derive(Debug)]
struct CaptureTransport {
    sent: Mutex<Vec<MessageEnvelope>>,
}

impl CaptureTransport {
    fn new() -> Self {
        Self {
            sent: Mutex::new(Vec::new()),
        }
    }
}

impl Transport for CaptureTransport {
    fn send<'a>(
        &'a self,
        envelope: MessageEnvelope,
    ) -> ping_core::transport::TransportFuture<'a, ()> {
        Box::pin(async move {
            self.sent.lock().push(envelope);
            Ok(())
        })
    }
    fn fetch_since<'a>(
        &'a self,
        _conv: ConversationId,
        _cursor: SyncCursor,
        _limit: u32,
    ) -> ping_core::transport::TransportFuture<'a, Vec<MessageEnvelope>> {
        Box::pin(async move { Ok(Vec::new()) })
    }
    fn subscribe<'a>(
        &'a self,
        _cb: Arc<dyn Fn(MessageEnvelope) + Send + Sync>,
    ) -> ping_core::transport::TransportFuture<'a, TransportSubscription> {
        Box::pin(async move { Ok(TransportSubscription::new(|| ())) })
    }
    fn discover_devices<'a>(
        &'a self,
        _user_id: ping_core::UserId,
    ) -> ping_core::transport::TransportFuture<'a, Vec<DiscoveredDevice>> {
        Box::pin(async move { Ok(Vec::new()) })
    }
}

fn make_client(label: &str, transport: Arc<CaptureTransport>) -> Arc<MessagingClient> {
    let cfg = ClientConfig {
        identity: Identity::generate(),
        device_label: label.into(),
        storage: Arc::new(MemStorage::default()) as Arc<dyn Storage>,
        transport,
        now_ms: 1_000,
        storage_backend: ping_core::StorageBackend::Memory,
        device_signing_secret_key: None,
    };
    block_on(MessagingClient::init(cfg)).expect("init")
}

#[test]
fn join_with_consumed_key_package_surfaces_key_package_not_found() {
    let ta = Arc::new(CaptureTransport::new());
    let tb = Arc::new(CaptureTransport::new());
    let a = make_client("A", ta.clone());
    let b = make_client("B", tb);

    let b_kp = block_on(b.fresh_key_package()).expect("b kp");
    let conv = block_on(a.create_conversation(Some("c".into()), 2_000)).expect("create");
    block_on(a.add_members(conv, vec![(b.device_id(), b_kp)], 3_000)).expect("add B");

    let welcome = ta
        .sent
        .lock()
        .iter()
        .find(|e| e.kind == MessageKind::Welcome)
        .cloned()
        .expect("welcome");

    // First join consumes B's single-use KeyPackage private key.
    block_on(b.join_conversation(&welcome, 4_000)).expect("first join");

    // Replaying the SAME Welcome now finds no matching KeyPackage in B's
    // keystore — this MUST be the typed KeyPackageNotFound, not opaque Mls.
    let err = block_on(b.join_conversation(&welcome, 5_000)).expect_err("second join must fail");
    assert!(
        matches!(err, Error::KeyPackageNotFound),
        "expected KeyPackageNotFound, got {err:?}"
    );
}
