//! Conversions between protobuf types and tidalDB domain types.

use tidaldb::replication::{
    WalSegmentId,
    shard::{RegionId, ShardId},
    transport::WalSegmentPayload,
};

use crate::proto;

// ── WalSegmentPayload <-> ShipSegmentRequest ─────────────────────────────

impl From<WalSegmentPayload> for proto::ShipSegmentRequest {
    fn from(p: WalSegmentPayload) -> Self {
        Self {
            id: Some(proto::WalSegmentId {
                region_id: p.id.region_id.0.into(),
                shard_id: p.id.shard_id.0.into(),
                seqno: p.id.seqno,
            }),
            payload: p.bytes,
            event_count: p.event_count,
            leader_last_seq: p.leader_last_seq,
        }
    }
}

impl TryFrom<proto::ShipSegmentRequest> for WalSegmentPayload {
    type Error = &'static str;

    fn try_from(req: proto::ShipSegmentRequest) -> Result<Self, Self::Error> {
        let id = req.id.ok_or("missing segment id")?;
        let region_id: u16 = id
            .region_id
            .try_into()
            .map_err(|_| "region_id exceeds u16 range")?;
        let shard_id: u16 = id
            .shard_id
            .try_into()
            .map_err(|_| "shard_id exceeds u16 range")?;
        Ok(Self {
            id: WalSegmentId::new(RegionId(region_id), ShardId(shard_id), id.seqno),
            bytes: req.payload,
            event_count: req.event_count,
            leader_last_seq: req.leader_last_seq,
        })
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)] // test assertions on known-good fixtures
mod tests {
    use super::*;

    #[test]
    fn roundtrip_payload() {
        let original = WalSegmentPayload {
            id: WalSegmentId::new(RegionId(2), ShardId(5), 42),
            bytes: vec![0xAB; 100],
            event_count: 7,
            leader_last_seq: 48,
        };

        let proto_req: proto::ShipSegmentRequest = original.into();
        assert_eq!(proto_req.event_count, 7);
        assert_eq!(proto_req.leader_last_seq, 48);

        let restored = WalSegmentPayload::try_from(proto_req).unwrap();
        assert_eq!(restored.id.region_id, RegionId(2));
        assert_eq!(restored.id.shard_id, ShardId(5));
        assert_eq!(restored.id.seqno, 42);
        assert_eq!(restored.bytes.len(), 100);
        assert_eq!(restored.event_count, 7);
        assert_eq!(
            restored.leader_last_seq, 48,
            "leader_last_seq must survive the proto roundtrip"
        );
    }

    /// An older sender that omits `leader_last_seq` decodes it as 0 ("unknown"),
    /// which the receiver treats as a fall-back to per-batch boundaries.
    #[test]
    fn missing_leader_last_seq_defaults_to_zero() {
        let req = proto::ShipSegmentRequest {
            id: Some(proto::WalSegmentId {
                region_id: 0,
                shard_id: 0,
                seqno: 1,
            }),
            payload: vec![1, 2, 3],
            event_count: 1,
            leader_last_seq: 0,
        };
        let restored = WalSegmentPayload::try_from(req).unwrap();
        assert_eq!(restored.leader_last_seq, 0);
    }

    #[test]
    fn missing_id_fails() {
        let req = proto::ShipSegmentRequest {
            id: None,
            payload: vec![],
            event_count: 0,
            leader_last_seq: 0,
        };
        assert!(WalSegmentPayload::try_from(req).is_err());
    }

    #[test]
    fn overflow_region_id_fails() {
        let req = proto::ShipSegmentRequest {
            id: Some(proto::WalSegmentId {
                region_id: 70_000,
                shard_id: 1,
                seqno: 1,
            }),
            payload: vec![],
            event_count: 0,
            leader_last_seq: 0,
        };
        assert!(WalSegmentPayload::try_from(req).is_err());
    }

    #[test]
    fn overflow_shard_id_fails() {
        let req = proto::ShipSegmentRequest {
            id: Some(proto::WalSegmentId {
                region_id: 1,
                shard_id: 70_000,
                seqno: 1,
            }),
            payload: vec![],
            event_count: 0,
            leader_last_seq: 0,
        };
        assert!(WalSegmentPayload::try_from(req).is_err());
    }
}
