//! Transport abstraction for WAL segment shipping between shards.
//!
//! The [`Transport`] trait defines how segments are sent and received between
//! nodes. Implementations include:
//! - [`super::in_process::InProcessTransport`]: crossbeam channels for tests
//!   and single-process multi-shard setups.
//! - (future) gRPC or TCP transport for multi-node deployments.

use crate::replication::segment_id::WalSegmentId;
use crate::replication::shard::ShardId;

/// A WAL segment ready for shipping to a peer shard.
///
/// Contains the raw batch bytes from a sealed WAL segment file, plus
/// metadata for tracking and idempotent replay on the receiver.
pub struct WalSegmentPayload {
    /// Globally unique identifier for this segment.
    pub id: WalSegmentId,
    /// Raw WAL bytes (one or more encoded batches).
    pub bytes: Vec<u8>,
    /// Total number of events across all batches in this segment.
    pub event_count: u64,
}

/// Errors that can occur during WAL segment transport.
#[derive(Debug, thiserror::Error)]
pub enum TransportError {
    /// The target shard is not known to this transport.
    #[error("unknown peer shard: {0}")]
    UnknownPeer(ShardId),
    /// The transport channel has been closed (peer disconnected or shutdown).
    #[error("transport channel closed")]
    Closed,
    /// The payload exceeds the transport's maximum size limit.
    #[error("payload too large: {size} bytes (max {max})")]
    PayloadTooLarge {
        /// Actual payload size in bytes.
        size: usize,
        /// Maximum allowed payload size in bytes.
        max: usize,
    },
}

/// Trait for shipping WAL segments between shards.
///
/// Implementations must be `Send + Sync + 'static` to allow sharing across
/// the shipper and receiver threads.
///
/// # Contract
///
/// - `send_segment` is non-blocking (best-effort delivery).
/// - `recv_segment` blocks until a segment is available or the transport shuts down.
/// - `local_shard` returns this node's shard identity.
pub trait Transport: Send + Sync + 'static {
    /// Ship a WAL segment payload to a peer shard.
    /// Blanket implementation for `Arc<dyn Transport>` so callers can use
    /// type-erased transports with the generic `spawn_shipper` / `spawn_receiver` APIs.
    ///
    /// # Errors
    ///
    /// - `TransportError::UnknownPeer` if `to` is not a recognized shard.
    /// - `TransportError::Closed` if the peer's receive channel is disconnected.
    /// - `TransportError::PayloadTooLarge` if the payload exceeds the size limit.
    fn send_segment(&self, to: ShardId, payload: WalSegmentPayload) -> Result<(), TransportError>;

    /// Block until a segment payload is received, or return `None` on shutdown.
    ///
    /// Returns `None` when the transport is closed and no more segments will
    /// arrive. The receiver thread should exit when this returns `None`.
    fn recv_segment(&self) -> Option<WalSegmentPayload>;

    /// The shard identity of this transport endpoint.
    fn local_shard(&self) -> ShardId;
}

/// Blanket implementation of `Transport` for `Arc<dyn Transport>`.
///
/// Allows type-erased `Arc<dyn Transport>` values to be passed to generic
/// APIs like `spawn_shipper<T: Transport>` and `spawn_receiver<T: Transport>`.
impl Transport for std::sync::Arc<dyn Transport> {
    fn send_segment(&self, to: ShardId, payload: WalSegmentPayload) -> Result<(), TransportError> {
        (**self).send_segment(to, payload)
    }

    fn recv_segment(&self) -> Option<WalSegmentPayload> {
        (**self).recv_segment()
    }

    fn local_shard(&self) -> ShardId {
        (**self).local_shard()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::replication::RegionId;

    #[test]
    fn transport_error_display_unknown_peer() {
        let err = TransportError::UnknownPeer(ShardId(5));
        assert!(err.to_string().contains("unknown peer shard"), "got: {err}");
    }

    #[test]
    fn transport_error_display_closed() {
        let err = TransportError::Closed;
        assert!(err.to_string().contains("closed"), "got: {err}");
    }

    #[test]
    fn transport_error_display_payload_too_large() {
        let err = TransportError::PayloadTooLarge { size: 100, max: 50 };
        let s = err.to_string();
        assert!(s.contains("100"), "got: {s}");
        assert!(s.contains("50"), "got: {s}");
    }

    #[test]
    fn wal_segment_payload_construction() {
        let payload = WalSegmentPayload {
            id: WalSegmentId::new(RegionId::SINGLE, ShardId::SINGLE, 42),
            bytes: vec![1, 2, 3],
            event_count: 1,
        };
        assert_eq!(payload.id.seqno, 42);
        assert_eq!(payload.bytes.len(), 3);
        assert_eq!(payload.event_count, 1);
    }
}
