//! Cross-project compatibility tests between WZP and featherChat.
//!
//! Verifies:
//! 1. Identity: same seed → same keys → same fingerprints (WZP-FC-8)
//! 2. CallSignal: WZP SignalMessage serializes into FC CallSignal.payload correctly
//! 3. Auth: WZP auth module request/response matches FC's /v1/auth/validate contract
//! 4. Mnemonic: BIP39 interop between both implementations

use wzp_proto::{KeyExchange, default_signal_version};

// ─── Identity Compatibility (WZP-FC-8) ──────────────────────────────────────

#[test]
fn same_seed_same_ed25519_key() {
    let seed = [42u8; 32];

    let wzp_kx = wzp_crypto::WarzoneKeyExchange::from_identity_seed(&seed);
    let wzp_pub = wzp_kx.identity_public_key();

    let fc_seed = warzone_protocol::identity::Seed::from_bytes(seed);
    let fc_id = fc_seed.derive_identity();
    let fc_pub = fc_id.signing.verifying_key();

    assert_eq!(&wzp_pub, fc_pub.as_bytes(), "Ed25519 keys must match");
}

#[test]
fn same_seed_same_fingerprint() {
    let seed = [99u8; 32];

    let wzp_kx = wzp_crypto::WarzoneKeyExchange::from_identity_seed(&seed);
    let wzp_fp = wzp_kx.fingerprint();

    let fc_seed = warzone_protocol::identity::Seed::from_bytes(seed);
    let fc_fp = fc_seed.derive_identity().public_identity().fingerprint.0;

    assert_eq!(wzp_fp, fc_fp, "Fingerprints must match");
}

#[test]
fn wzp_identity_module_matches_featherchat() {
    let seed = [0xAB; 32];

    let wzp_pub = wzp_crypto::Seed::from_bytes(seed)
        .derive_identity()
        .public_identity();

    let fc_pub = warzone_protocol::identity::Seed::from_bytes(seed)
        .derive_identity()
        .public_identity();

    assert_eq!(wzp_pub.signing.as_bytes(), fc_pub.signing.as_bytes());
    assert_eq!(wzp_pub.encryption.as_bytes(), fc_pub.encryption.as_bytes());
    assert_eq!(wzp_pub.fingerprint.0, fc_pub.fingerprint.0);
    assert_eq!(
        wzp_pub.fingerprint.to_string(),
        fc_pub.fingerprint.to_string()
    );
}

#[test]
fn random_seed_identity_match() {
    let fc_seed = warzone_protocol::identity::Seed::generate();
    let raw = fc_seed.0;

    let fc_fp = fc_seed.derive_identity().public_identity().fingerprint.0;
    let wzp_fp = wzp_crypto::WarzoneKeyExchange::from_identity_seed(&raw).fingerprint();

    assert_eq!(wzp_fp, fc_fp);
}

#[test]
fn hkdf_derive_matches() {
    let seed = [0x55; 32];

    let fc_ed = warzone_protocol::crypto::hkdf_derive(&seed, b"", b"warzone-ed25519", 32);
    let fc_signing = ed25519_dalek::SigningKey::from_bytes(&fc_ed.try_into().unwrap());
    let fc_pub = fc_signing.verifying_key();

    let wzp_pub = wzp_crypto::WarzoneKeyExchange::from_identity_seed(&seed).identity_public_key();

    assert_eq!(&wzp_pub, fc_pub.as_bytes());
}

// ─── BIP39 Mnemonic Interop ─────────────────────────────────────────────────

#[test]
fn mnemonic_roundtrip_fc_to_wzp() {
    let seed = [0x77; 32];
    let fc_mnemonic = warzone_protocol::identity::Seed::from_bytes(seed).to_mnemonic();
    let wzp_recovered = wzp_crypto::Seed::from_mnemonic(&fc_mnemonic).unwrap();
    assert_eq!(wzp_recovered.0, seed);
}

#[test]
fn mnemonic_roundtrip_wzp_to_fc() {
    let seed = [0x33; 32];
    let wzp_mnemonic = wzp_crypto::Seed::from_bytes(seed).to_mnemonic();
    let fc_recovered = warzone_protocol::identity::Seed::from_mnemonic(&wzp_mnemonic).unwrap();
    assert_eq!(fc_recovered.0, seed);
}

#[test]
fn mnemonic_strings_identical() {
    let seed = [0xDE; 32];
    let fc_words = warzone_protocol::identity::Seed::from_bytes(seed).to_mnemonic();
    let wzp_words = wzp_crypto::Seed::from_bytes(seed).to_mnemonic();
    assert_eq!(fc_words, wzp_words);
}

// ─── CallSignal Payload Interop ─────────────────────────────────────────────

#[test]
fn wzp_signal_serializes_into_fc_callsignal_payload() {
    // WZP creates a CallOffer SignalMessage
    let offer = wzp_proto::SignalMessage::CallOffer {
        version: default_signal_version(),
        identity_pub: [1u8; 32],
        ephemeral_pub: [2u8; 32],
        signature: vec![3u8; 64],
        supported_profiles: vec![wzp_proto::QualityProfile::GOOD],
        alias: None,
        protocol_version: 2,
        supported_versions: vec![2],
    };

    // Encode as featherChat CallSignal payload
    let payload = wzp_client::featherchat::encode_call_payload(
        &offer,
        Some("relay.example.com:4433"),
        Some("myroom"),
    );

    // Verify it's valid JSON
    let parsed: serde_json::Value = serde_json::from_str(&payload).unwrap();
    assert!(parsed.get("signal").is_some());
    assert_eq!(parsed["relay_addr"], "relay.example.com:4433");
    assert_eq!(parsed["room"], "myroom");

    // featherChat would put this in WireMessage::CallSignal { payload, ... }
    // Verify the FC side can create a CallSignal with this payload
    let fc_msg = warzone_protocol::message::WireMessage::CallSignal {
        id: "call-123".to_string(),
        sender_fingerprint: "abcd1234".to_string(),
        signal_type: warzone_protocol::message::CallSignalType::Offer,
        payload: payload.clone(),
        target: "peer-fingerprint".to_string(),
    };

    // Verify it serializes with bincode (FC's wire format)
    let encoded = bincode::serialize(&fc_msg).unwrap();
    assert!(!encoded.is_empty());

    // And deserializes back
    let decoded: warzone_protocol::message::WireMessage = bincode::deserialize(&encoded).unwrap();
    if let warzone_protocol::message::WireMessage::CallSignal {
        id,
        payload: p,
        signal_type,
        ..
    } = decoded
    {
        assert_eq!(id, "call-123");
        assert!(matches!(
            signal_type,
            warzone_protocol::message::CallSignalType::Offer
        ));

        // Decode the WZP payload back
        let wzp_payload = wzp_client::featherchat::decode_call_payload(&p).unwrap();
        assert_eq!(wzp_payload.relay_addr.unwrap(), "relay.example.com:4433");
        assert!(matches!(
            wzp_payload.signal,
            wzp_proto::SignalMessage::CallOffer { .. }
        ));
    } else {
        panic!("expected CallSignal");
    }
}

#[test]
fn wzp_answer_round_trips_through_fc_callsignal() {
    let answer = wzp_proto::SignalMessage::CallAnswer {
        version: default_signal_version(),
        identity_pub: [10u8; 32],
        ephemeral_pub: [20u8; 32],
        signature: vec![30u8; 64],
        chosen_profile: wzp_proto::QualityProfile::DEGRADED,
    };

    let payload = wzp_client::featherchat::encode_call_payload(&answer, None, None);

    let fc_msg = warzone_protocol::message::WireMessage::CallSignal {
        id: "call-456".to_string(),
        sender_fingerprint: "efgh5678".to_string(),
        signal_type: warzone_protocol::message::CallSignalType::Answer,
        payload,
        target: "caller-fp".to_string(),
    };

    let bytes = bincode::serialize(&fc_msg).unwrap();
    let decoded: warzone_protocol::message::WireMessage = bincode::deserialize(&bytes).unwrap();

    if let warzone_protocol::message::WireMessage::CallSignal { payload, .. } = decoded {
        let wzp = wzp_client::featherchat::decode_call_payload(&payload).unwrap();
        if let wzp_proto::SignalMessage::CallAnswer { chosen_profile, .. } = wzp.signal {
            assert_eq!(chosen_profile.codec, wzp_proto::CodecId::Opus6k);
        } else {
            panic!("expected CallAnswer");
        }
    }
}

#[test]
fn wzp_hangup_round_trips_through_fc_callsignal() {
    let hangup = wzp_proto::SignalMessage::Hangup {
        version: default_signal_version(),
        reason: wzp_proto::HangupReason::Normal,
        call_id: None,
    };

    let payload = wzp_client::featherchat::encode_call_payload(&hangup, None, None);
    let signal_type = wzp_client::featherchat::signal_to_call_type(&hangup);
    assert!(matches!(
        signal_type,
        wzp_client::featherchat::CallSignalType::Hangup
    ));

    let fc_msg = warzone_protocol::message::WireMessage::CallSignal {
        id: "call-789".to_string(),
        sender_fingerprint: "xyz".to_string(),
        signal_type: warzone_protocol::message::CallSignalType::Hangup,
        payload,
        target: "peer".to_string(),
    };

    let bytes = bincode::serialize(&fc_msg).unwrap();
    let decoded: warzone_protocol::message::WireMessage = bincode::deserialize(&bytes).unwrap();

    if let warzone_protocol::message::WireMessage::CallSignal { payload, .. } = decoded {
        let wzp = wzp_client::featherchat::decode_call_payload(&payload).unwrap();
        assert!(matches!(
            wzp.signal,
            wzp_proto::SignalMessage::Hangup { .. }
        ));
    }
}

// ─── Auth Token Contract ────────────────────────────────────────────────────

#[test]
fn auth_validate_request_matches_fc_contract() {
    // WZP sends: { "token": "..." }
    // FC expects: ValidateRequest { token: String }
    let wzp_request = serde_json::json!({ "token": "test-token-123" });
    let json_str = wzp_request.to_string();

    // FC can deserialize this (same shape as their ValidateRequest)
    #[derive(serde::Deserialize)]
    struct FcValidateRequest {
        token: String,
    }
    let fc_req: FcValidateRequest = serde_json::from_str(&json_str).unwrap();
    assert_eq!(fc_req.token, "test-token-123");
}

#[test]
fn auth_validate_response_matches_wzp_expectations() {
    // FC returns: { "valid": true, "fingerprint": "...", "alias": "..." }
    // WZP expects: wzp_relay::auth::ValidateResponse
    let fc_response = serde_json::json!({
        "valid": true,
        "fingerprint": "a3f8:1b2c:3d4e:5f60:7182:93a4:b5c6:d7e8",
        "alias": "manwe",
        "eth_address": null
    });

    let wzp_resp: wzp_relay::auth::ValidateResponse = serde_json::from_value(fc_response).unwrap();
    assert!(wzp_resp.valid);
    assert_eq!(
        wzp_resp.fingerprint.unwrap(),
        "a3f8:1b2c:3d4e:5f60:7182:93a4:b5c6:d7e8"
    );
    assert_eq!(wzp_resp.alias.unwrap(), "manwe");
}

#[test]
fn auth_invalid_response_matches() {
    let fc_response = serde_json::json!({ "valid": false });
    let wzp_resp: wzp_relay::auth::ValidateResponse = serde_json::from_value(fc_response).unwrap();
    assert!(!wzp_resp.valid);
    assert!(wzp_resp.fingerprint.is_none());
}

// ─── Signal Type Mapping ────────────────────────────────────────────────────

#[test]
fn all_signal_types_map_correctly() {
    use wzp_client::featherchat::signal_to_call_type;

    let cases: Vec<(wzp_proto::SignalMessage, &str)> = vec![
        (
            wzp_proto::SignalMessage::CallOffer {
                version: default_signal_version(),
                identity_pub: [0; 32],
                ephemeral_pub: [0; 32],
                signature: vec![],
                supported_profiles: vec![],
                alias: None,
                protocol_version: 2,
                supported_versions: vec![2],
            },
            "Offer",
        ),
        (
            wzp_proto::SignalMessage::CallAnswer {
                version: default_signal_version(),
                identity_pub: [0; 32],
                ephemeral_pub: [0; 32],
                signature: vec![],
                chosen_profile: wzp_proto::QualityProfile::GOOD,
            },
            "Answer",
        ),
        (
            wzp_proto::SignalMessage::IceCandidate {
                version: default_signal_version(),
                candidate: "candidate:1".to_string(),
            },
            "IceCandidate",
        ),
        (
            wzp_proto::SignalMessage::Hangup {
                version: default_signal_version(),
                reason: wzp_proto::HangupReason::Normal,
                call_id: None,
            },
            "Hangup",
        ),
    ];

    for (signal, expected_name) in cases {
        let ct = signal_to_call_type(&signal);
        let name = format!("{ct:?}");
        assert_eq!(
            name, expected_name,
            "signal type mapping for {expected_name}"
        );
    }
}

// ─── Room Hashing + Access Control ─────────────────────────────────────────

#[test]
fn hash_room_name_deterministic() {
    let h1 = wzp_crypto::hash_room_name("ops-channel");
    let h2 = wzp_crypto::hash_room_name("ops-channel");
    assert_eq!(h1, h2, "same input must produce same hash");
}

#[test]
fn hash_room_name_is_32_hex_chars() {
    let h = wzp_crypto::hash_room_name("test-room");
    assert_eq!(h.len(), 32, "hash must be 32 hex chars (16 bytes)");
    assert!(
        h.chars().all(|c| c.is_ascii_hexdigit()),
        "hash must contain only hex characters, got: {h}"
    );
}

#[test]
fn hash_room_name_different_inputs() {
    let h1 = wzp_crypto::hash_room_name("alpha");
    let h2 = wzp_crypto::hash_room_name("beta");
    let h3 = wzp_crypto::hash_room_name("alpha-2");
    assert_ne!(h1, h2, "different names must produce different hashes");
    assert_ne!(h1, h3);
    assert_ne!(h2, h3);
}

#[test]
fn hash_room_name_matches_fc_convention() {
    // Manual SHA-256("featherchat-group:" + name)[:16] using the sha2 crate directly
    use sha2::{Digest, Sha256};

    let name = "warzone-squad";
    let mut hasher = Sha256::new();
    hasher.update(b"featherchat-group:");
    hasher.update(name.as_bytes());
    let digest = hasher.finalize();
    let expected = hex::encode(&digest[..16]);

    let actual = wzp_crypto::hash_room_name(name);
    assert_eq!(
        actual, expected,
        "hash_room_name must equal SHA-256('featherchat-group:' + name)[:16]"
    );
}

#[test]
fn room_acl_open_mode() {
    let mgr = wzp_relay::room::RoomManager::new();
    // Open mode: everyone is authorized regardless of fingerprint presence
    assert!(mgr.is_authorized("any-room", None));
    assert!(mgr.is_authorized("any-room", Some("random-fp")));
    assert!(mgr.is_authorized("another-room", Some("abc:def")));
}

#[test]
fn room_acl_enforced() {
    let mgr = wzp_relay::room::RoomManager::with_acl();
    // ACL enabled but no fingerprint provided => denied
    assert!(
        !mgr.is_authorized("room1", None),
        "ACL mode must reject connections without a fingerprint"
    );
}

#[test]
fn room_acl_allows_listed() {
    let mut mgr = wzp_relay::room::RoomManager::with_acl();
    mgr.allow("secure-room", "alice-fp");
    mgr.allow("secure-room", "bob-fp");

    assert!(mgr.is_authorized("secure-room", Some("alice-fp")));
    assert!(mgr.is_authorized("secure-room", Some("bob-fp")));
}

#[test]
fn room_acl_denies_unlisted() {
    let mut mgr = wzp_relay::room::RoomManager::with_acl();
    mgr.allow("secure-room", "alice-fp");

    assert!(
        !mgr.is_authorized("secure-room", Some("eve-fp")),
        "unlisted fingerprints must be denied"
    );
    assert!(
        !mgr.is_authorized("secure-room", Some("mallory-fp")),
        "unlisted fingerprints must be denied"
    );
    // No fingerprint at all => also denied
    assert!(
        !mgr.is_authorized("secure-room", None),
        "no fingerprint must be denied in ACL mode"
    );
}

// ─── Web Bridge Auth + Proto Standalone + S-9 ──────────────────────────────

/// WZP-S-6: featherChat may include `eth_address` in ValidateResponse.
/// WZP's ValidateResponse must handle it gracefully (serde ignores unknown fields).
#[test]
fn auth_response_with_eth_address() {
    // FC response with eth_address present (non-null)
    let with_eth = serde_json::json!({
        "valid": true,
        "fingerprint": "a1b2:c3d4:e5f6:7890:abcd:ef01:2345:6789",
        "alias": "vitalik",
        "eth_address": "0x1234567890abcdef1234567890abcdef12345678"
    });
    let resp: wzp_relay::auth::ValidateResponse = serde_json::from_value(with_eth).unwrap();
    assert!(resp.valid);
    assert_eq!(
        resp.fingerprint.unwrap(),
        "a1b2:c3d4:e5f6:7890:abcd:ef01:2345:6789"
    );
    assert_eq!(resp.alias.unwrap(), "vitalik");

    // FC response with eth_address = null
    let with_null_eth = serde_json::json!({
        "valid": true,
        "fingerprint": "dead:beef:cafe:babe:1234:5678:9abc:def0",
        "alias": "anon",
        "eth_address": null
    });
    let resp2: wzp_relay::auth::ValidateResponse = serde_json::from_value(with_null_eth).unwrap();
    assert!(resp2.valid);
    assert_eq!(
        resp2.fingerprint.unwrap(),
        "dead:beef:cafe:babe:1234:5678:9abc:def0"
    );

    // FC response without eth_address at all
    let without_eth = serde_json::json!({
        "valid": false
    });
    let resp3: wzp_relay::auth::ValidateResponse = serde_json::from_value(without_eth).unwrap();
    assert!(!resp3.valid);
}

/// WZP-S-7: SignalMessage::AuthToken { version: default_signal_version(),  token } exists and round-trips via serde.
#[test]
fn wzp_proto_has_auth_token_variant() {
    let msg = wzp_proto::SignalMessage::AuthToken {
        version: default_signal_version(),
        token: "fc-bearer-token-xyz".to_string(),
    };

    // Serialize to JSON
    let json = serde_json::to_string(&msg).unwrap();
    assert!(json.contains("AuthToken"));
    assert!(json.contains("fc-bearer-token-xyz"));

    // Deserialize back
    let decoded: wzp_proto::SignalMessage = serde_json::from_str(&json).unwrap();
    if let wzp_proto::SignalMessage::AuthToken { token, .. } = decoded {
        assert_eq!(token, "fc-bearer-token-xyz");
    } else {
        panic!("expected AuthToken variant, got: {decoded:?}");
    }
}

/// WZP-S-6: WZP CallSignalType has all variants matching featherChat's set.
#[test]
fn all_fc_call_signal_types_representable() {
    use wzp_client::featherchat::CallSignalType;

    // Verify each FC variant can be constructed and debug-printed
    let variants: Vec<(CallSignalType, &str)> = vec![
        (CallSignalType::Offer, "Offer"),
        (CallSignalType::Answer, "Answer"),
        (CallSignalType::IceCandidate, "IceCandidate"),
        (CallSignalType::Hangup, "Hangup"),
        (CallSignalType::Reject, "Reject"),
        (CallSignalType::Ringing, "Ringing"),
        (CallSignalType::Busy, "Busy"),
    ];

    assert_eq!(
        variants.len(),
        7,
        "featherChat defines exactly 7 call signal types"
    );

    for (variant, expected_name) in &variants {
        let name = format!("{variant:?}");
        assert_eq!(&name, expected_name);

        // Each variant should serialize/deserialize cleanly
        let json = serde_json::to_string(variant).unwrap();
        let round_tripped: CallSignalType = serde_json::from_str(&json).unwrap();
        assert_eq!(format!("{round_tripped:?}"), *expected_name);
    }
}

/// WZP-S-9: hashed room name used as QUIC SNI must be valid — lowercase hex only.
#[test]
fn hash_room_name_used_as_sni_is_valid() {
    let long_name = "x".repeat(1000);
    let test_rooms = [
        "general",
        "Voice Room #1",
        "café-lounge",
        "a]b[c{d}e",
        "\u{1f480}\u{1f525}",
        long_name.as_str(),
    ];

    for room in &test_rooms {
        let hashed = wzp_crypto::hash_room_name(room);

        // Must be non-empty
        assert!(!hashed.is_empty(), "hash of '{room}' must not be empty");

        // Must contain only lowercase hex chars (valid for SNI)
        for ch in hashed.chars() {
            assert!(
                ch.is_ascii_hexdigit() && !ch.is_ascii_uppercase(),
                "hash of '{room}' contains invalid SNI char: '{ch}' (full: {hashed})"
            );
        }

        // SHA-256 truncated to 16 bytes -> 32 hex chars
        assert_eq!(
            hashed.len(),
            32,
            "hash should be 32 hex chars (16 bytes), got {} for '{room}'",
            hashed.len()
        );
    }
}

/// WZP-S-7: wzp-proto Cargo.toml must be standalone — no `.workspace = true` inheritance.
#[test]
fn wzp_proto_cargo_toml_is_standalone() {
    // Try both paths (run from workspace root or from crate directory)
    let candidates = ["crates/wzp-proto/Cargo.toml", "../wzp-proto/Cargo.toml"];

    let contents = candidates
        .iter()
        .find_map(|p| std::fs::read_to_string(p).ok())
        .expect("could not read crates/wzp-proto/Cargo.toml from any expected path");

    // Must NOT contain ".workspace = true" anywhere — that would break standalone use
    assert!(
        !contents.contains(".workspace = true"),
        "wzp-proto Cargo.toml must not use workspace inheritance (.workspace = true), \
         found in:\n{contents}"
    );

    // Sanity: it should still be a valid Cargo.toml with the right package name
    assert!(
        contents.contains("name = \"wzp-proto\""),
        "expected package name 'wzp-proto' in Cargo.toml"
    );
}
