//! Last-resort KeyPackage (RFC 9420 §10).
//!
//! A last-resort KeyPackage must be a fully valid join credential — a peer can
//! add the device with it and the device can join via the resulting Welcome —
//! AND it must carry the `LastResort` extension so the server knows it may be
//! served repeatedly as the always-available fallback when the single-use pool
//! is empty.

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

use std::sync::Arc;

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

use openmls::prelude::{tls_codec::Deserialize as _, MlsMessageIn, ProtocolVersion};
use openmls_rust_crypto::OpenMlsRustCrypto;
use openmls_traits::OpenMlsProvider as _;

use ping_core::{
    storage::memory::MemStorage, transport::DiscoveredDevice, transport::TransportSubscription,
    ClientConfig, ConversationId, 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 last_resort_key_package_carries_extension() {
    let t = Arc::new(CaptureTransport::new());
    let c = make_client("LR", t);
    let kp_bytes = block_on(c.fresh_last_resort_key_package()).expect("last-resort kp");

    let provider = OpenMlsRustCrypto::default();
    assert!(
        is_last_resort(&kp_bytes, &provider),
        "fresh_last_resort_key_package must set the LastResort extension"
    );

    // A normal KeyPackage must NOT be flagged last-resort.
    let normal_bytes = block_on(c.fresh_key_package()).expect("normal kp");
    assert!(
        !is_last_resort(&normal_bytes, &provider),
        "fresh_key_package must NOT be flagged last-resort"
    );
}

fn is_last_resort(kp_bytes: &[u8], provider: &OpenMlsRustCrypto) -> bool {
    let msg = MlsMessageIn::tls_deserialize(&mut &*kp_bytes).expect("deserialize");
    let kp_in = match msg.extract() {
        openmls::prelude::MlsMessageBodyIn::KeyPackage(kp) => kp,
        other => panic!("expected KeyPackage body, got {other:?}"),
    };
    kp_in
        .validate(provider.crypto(), ProtocolVersion::Mls10)
        .expect("validate key package")
        .last_resort()
}

#[test]
fn last_resort_key_package_is_a_valid_join_credential() {
    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);

    // B publishes a LAST-RESORT KeyPackage; A invites B with it.
    let b_kp = block_on(b.fresh_last_resort_key_package()).expect("b last-resort kp");
    let conv = block_on(a.create_conversation(Some("lr".into()), 2_000)).expect("create");
    block_on(a.add_members(conv, vec![(b.device_id(), b_kp)], 3_000))
        .expect("add B via last-resort");

    let welcome = ta
        .sent
        .lock()
        .iter()
        .find(|e| e.kind == MessageKind::Welcome)
        .cloned()
        .expect("welcome captured");
    // B joins via the Welcome bound to its last-resort KeyPackage.
    block_on(b.join_conversation(&welcome, 4_000)).expect("B join via last-resort kp");
    assert!(
        b.list_conversations().iter().any(|m| m.id == conv),
        "B must be a member after joining via the last-resort KeyPackage"
    );
}
