//! Relay-side transcoding between binary and JSON encodings.
//!
//! When two peers negotiate different encodings (e.g., Rust client uses
//! `psl-binary` and a TypeScript debugging client uses `json`), the relay
//! transcodes control/RPC payloads between them transparently.
//!
//! **STREAM_DATA payloads are NOT transcoded** — they are forwarded as raw
//! bytes regardless of encoding mode. Only typed control payloads and RPC
//! envelopes go through this module.

use pulse_core::codec::{
    CodecError, Encoding, PulseDeserialize, PulseSerialize,
};

/// Transcode a typed payload from one encoding to another.
///
/// Returns the original bytes if `from == to` (zero-copy fast path).
///
/// # Performance
///
/// Target: < 500ns per packet for simple control payloads.
/// This is acceptable because transcoding only applies to control packets
/// (CONNECT, ACCEPT, STREAM_OPEN) and RPC envelopes — not bulk data.
pub fn transcode_payload<T: PulseSerialize + PulseDeserialize>(
    bytes: &[u8],
    from: Encoding,
    to: Encoding,
) -> Result<Vec<u8>, CodecError> {
    // Fast path: same encoding, just clone.
    if from == to {
        return Ok(bytes.to_vec());
    }

    // Decode from source encoding, re-encode to target encoding.
    let value = T::decode_with(bytes, from)?;
    value.encode_with(to)
}

/// Check if transcoding is needed between two session encodings.
///
/// Returns `true` if the two sessions are using different encodings
/// and payloads need to be re-encoded when forwarding between them.
#[inline]
pub fn needs_transcoding(sender_encoding: Encoding, receiver_encoding: Encoding) -> bool {
    sender_encoding != receiver_encoding
}

/// Transcode session metadata for logging/debugging.
///
/// When a relay session is established, this returns a human-readable
/// description of the encoding bridge (or "direct" if no transcoding needed).
pub fn describe_bridge(sender: Encoding, receiver: Encoding) -> &'static str {
    match (sender, receiver) {
        (Encoding::PslBinary, Encoding::PslBinary) => "binary→binary (direct)",
        (Encoding::Json, Encoding::Json) => "json→json (direct)",
        (Encoding::PslBinary, Encoding::Json) => "binary→json (transcoding)",
        (Encoding::Json, Encoding::PslBinary) => "json→binary (transcoding)",
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pulse_core::control::{ConnectPayload, AcceptPayload, StreamStatus, RejectPayload};

    #[test]
    fn transcode_connect_binary_to_json() {
        let payload = ConnectPayload {
            version: 1,
            token: "test_token".into(),
            capabilities: vec!["plp/1".into()],
            streams: vec![],
            encoding: Some("psl-binary".into()),
        };

        let binary = payload.encode_with(Encoding::PslBinary).unwrap();
        let json = transcode_payload::<ConnectPayload>(
            &binary,
            Encoding::PslBinary,
            Encoding::Json,
        )
        .unwrap();

        let json_str = std::str::from_utf8(&json).unwrap();
        assert!(json_str.contains("\"token\":\"test_token\""));

        // Decode the JSON and verify
        let decoded = ConnectPayload::decode_with(&json, Encoding::Json).unwrap();
        assert_eq!(decoded.token, "test_token");
    }

    #[test]
    fn transcode_accept_json_to_binary() {
        let payload = AcceptPayload {
            session_id: "sess-123".into(),
            streams: vec![StreamStatus {
                id: 1,
                stream_type: "audio".into(),
                accepted: true,
            }],
            relay_path: vec!["relay-1".into()],
            metrics_endpoint: "/metrics".into(),
            encoding: Some("json".into()),
        };

        let json = payload.encode_with(Encoding::Json).unwrap();
        let binary = transcode_payload::<AcceptPayload>(
            &json,
            Encoding::Json,
            Encoding::PslBinary,
        )
        .unwrap();

        let decoded = AcceptPayload::decode_with(&binary, Encoding::PslBinary).unwrap();
        assert_eq!(decoded.session_id, "sess-123");
        assert_eq!(decoded.streams.len(), 1);
        assert!(decoded.streams[0].accepted);
    }

    #[test]
    fn transcode_same_encoding_is_noop() {
        let payload = RejectPayload {
            reason: "forbidden".into(),
        };

        let binary = payload.encode_with(Encoding::PslBinary).unwrap();
        let result = transcode_payload::<RejectPayload>(
            &binary,
            Encoding::PslBinary,
            Encoding::PslBinary,
        )
        .unwrap();

        assert_eq!(binary, result);
    }

    #[test]
    fn needs_transcoding_check() {
        assert!(!needs_transcoding(Encoding::PslBinary, Encoding::PslBinary));
        assert!(!needs_transcoding(Encoding::Json, Encoding::Json));
        assert!(needs_transcoding(Encoding::PslBinary, Encoding::Json));
        assert!(needs_transcoding(Encoding::Json, Encoding::PslBinary));
    }

    #[test]
    fn bridge_descriptions() {
        assert_eq!(
            describe_bridge(Encoding::PslBinary, Encoding::Json),
            "binary→json (transcoding)"
        );
        assert_eq!(
            describe_bridge(Encoding::PslBinary, Encoding::PslBinary),
            "binary→binary (direct)"
        );
    }

    #[test]
    fn roundtrip_through_both_transcodings() {
        let payload = ConnectPayload {
            version: 1,
            token: "roundtrip".into(),
            capabilities: vec!["plp/1".into(), "mesh".into()],
            streams: vec![],
            encoding: Some("psl-binary".into()),
        };

        // Binary → JSON → Binary
        let binary = payload.encode_with(Encoding::PslBinary).unwrap();
        let json = transcode_payload::<ConnectPayload>(
            &binary,
            Encoding::PslBinary,
            Encoding::Json,
        )
        .unwrap();
        let back = transcode_payload::<ConnectPayload>(
            &json,
            Encoding::Json,
            Encoding::PslBinary,
        )
        .unwrap();

        let decoded = ConnectPayload::decode_with(&back, Encoding::PslBinary).unwrap();
        assert_eq!(decoded, payload);
    }
}
