//! CR-4 conformance runner — Rust binding.
//!
//! Re-asserts the named scenarios in `scenario.json` against this binding's facade.
//! Other bindings (Swift/Kotlin via UniFFI, JS via WASM once IndexedDB lands) build
//! the same scenarios from the same JSON.

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

use std::path::PathBuf;
use std::sync::Arc;

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

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

fn fixture_path() -> PathBuf {
    let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    p.push("../../protocol/conformance/cr4_persistent_provider/scenario.json");
    p
}

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

impl Transport for CapturingTransport {
    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 open_client(
    transport: Arc<CapturingTransport>,
    host_storage: Arc<dyn Storage>,
    identity: Identity,
    backend: StorageBackend,
) -> Arc<MessagingClient> {
    let cfg = ClientConfig {
        identity,
        device_label: "conformance".into(),
        storage: host_storage,
        transport,
        now_ms: 1_000,
        storage_backend: backend,
        device_signing_secret_key: None,
    };
    block_on(MessagingClient::init(cfg)).expect("init")
}

#[test]
fn cr4_conformance_scenarios() {
    // The fixture file must be present and parseable. Body of the scenarios is
    // re-asserted programmatically below.
    let json =
        std::fs::read_to_string(fixture_path()).unwrap_or_else(|e| panic!("read fixture: {e}"));
    for required in [
        "sqlite_backend_persists_across_restart",
        "memory_backend_does_not_persist",
        "wrong_encryption_key_rejected",
    ] {
        assert!(
            json.contains(required),
            "fixture missing scenario {required}"
        );
    }

    // --- sqlite_backend_persists_across_restart ---
    {
        let tmp = TempDir::new().expect("tempdir");
        let b_path = tmp.path().join("b.sqlite");
        let transport_a = Arc::new(CapturingTransport::default());
        let transport_b = Arc::new(CapturingTransport::default());

        let a_identity = Identity::generate();
        let b_identity_export = Identity::generate().export();
        let b_host = Arc::new(MemStorage::default()) as Arc<dyn Storage>;

        let client_a = open_client(
            transport_a.clone(),
            Arc::new(MemStorage::default()) as Arc<dyn Storage>,
            a_identity,
            StorageBackend::Sqlite {
                path: tmp.path().join("a.sqlite"),
                encryption_key: None,
            },
        );
        let client_b = open_client(
            transport_b.clone(),
            b_host.clone(),
            Identity::import(&b_identity_export).unwrap(),
            StorageBackend::Sqlite {
                path: b_path.clone(),
                encryption_key: None,
            },
        );

        let conv = block_on(client_a.create_conversation(Some("cr4-conf".into()), 1_000)).unwrap();
        let kp = client_b.fresh_key_package().unwrap();
        block_on(client_a.add_members(conv, vec![(client_b.device_id(), kp)], 2_000)).unwrap();
        let welcome = transport_a
            .sent
            .lock()
            .iter()
            .rev()
            .find(|e| matches!(e.kind, ping_core::message::MessageKind::Welcome))
            .cloned()
            .expect("welcome");
        block_on(client_b.join_conversation(&welcome, 3_000)).unwrap();
        let env1 = block_on(client_a.send(conv, b"hello-1".to_vec(), 4_000)).unwrap();
        block_on(client_b.process_envelope(&env1, 5_000)).unwrap();

        drop(client_b);
        let client_b2 = open_client(
            transport_b.clone(),
            b_host.clone(),
            Identity::import(&b_identity_export).unwrap(),
            StorageBackend::Sqlite {
                path: b_path.clone(),
                encryption_key: None,
            },
        );
        let env2 = block_on(client_a.send(conv, b"hello-2".to_vec(), 6_000)).unwrap();
        let msg = block_on(client_b2.process_envelope(&env2, 7_000))
            .expect("decrypt after cold restart")
            .expect("application msg");
        assert_eq!(msg.plaintext, b"hello-2");
    }

    // --- memory_backend_does_not_persist ---
    {
        let transport_a = Arc::new(CapturingTransport::default());
        let transport_b = Arc::new(CapturingTransport::default());
        let b_identity_export = Identity::generate().export();
        let b_host = Arc::new(MemStorage::default()) as Arc<dyn Storage>;

        let client_a = open_client(
            transport_a.clone(),
            Arc::new(MemStorage::default()) as Arc<dyn Storage>,
            Identity::generate(),
            StorageBackend::Memory,
        );
        let client_b = open_client(
            transport_b.clone(),
            b_host.clone(),
            Identity::import(&b_identity_export).unwrap(),
            StorageBackend::Memory,
        );

        let conv = block_on(client_a.create_conversation(None, 1_000)).unwrap();
        let kp = client_b.fresh_key_package().unwrap();
        block_on(client_a.add_members(conv, vec![(client_b.device_id(), kp)], 2_000)).unwrap();
        let welcome = transport_a
            .sent
            .lock()
            .iter()
            .rev()
            .find(|e| matches!(e.kind, ping_core::message::MessageKind::Welcome))
            .cloned()
            .expect("welcome");
        block_on(client_b.join_conversation(&welcome, 3_000)).unwrap();
        let env = block_on(client_a.send(conv, b"warm".to_vec(), 4_000)).unwrap();
        block_on(client_b.process_envelope(&env, 5_000)).unwrap();

        drop(client_b);
        let client_b2 = open_client(
            transport_b.clone(),
            b_host.clone(),
            Identity::import(&b_identity_export).unwrap(),
            StorageBackend::Memory,
        );
        let env_after = block_on(client_a.send(conv, b"cold".to_vec(), 6_000)).unwrap();
        let res = block_on(client_b2.process_envelope(&env_after, 7_000));
        assert!(res.is_err(), "memory backend must NOT survive restart");
    }

    // --- wrong_encryption_key_rejected ---
    {
        let tmp = TempDir::new().expect("tempdir");
        let path = tmp.path().join("encrypted.sqlite");
        let identity_export = Identity::generate().export();
        let host = Arc::new(MemStorage::default()) as Arc<dyn Storage>;
        let transport = Arc::new(CapturingTransport::default());

        {
            let client = open_client(
                transport.clone(),
                host.clone(),
                Identity::import(&identity_export).unwrap(),
                StorageBackend::Sqlite {
                    path: path.clone(),
                    encryption_key: Some(zeroize::Zeroizing::new([0x11u8; 32])),
                },
            );
            let _ = block_on(client.create_conversation(None, 1_000)).unwrap();
        }

        let cfg = ClientConfig {
            identity: Identity::import(&identity_export).unwrap(),
            device_label: "wrong-key".into(),
            storage: host,
            transport,
            now_ms: 2_000,
            storage_backend: StorageBackend::Sqlite {
                path: path.clone(),
                encryption_key: Some(zeroize::Zeroizing::new([0x22u8; 32])),
            },
            device_signing_secret_key: None,
        };
        assert!(
            block_on(MessagingClient::init(cfg)).is_err(),
            "wrong SQLCipher key must reject init"
        );
    }
}
