//! Transport abstraction for pluggable mesh backends.
//!
//! Every mesh transport (iroh QUIC, TCP, LoRa, Serial) implements the
//! [`MeshTransport`] trait. The [`TransportAddr`] enum provides a
//! transport-agnostic address type.

use std::fmt;

use anyhow::Result;

/// Transport-agnostic peer address.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum TransportAddr {
    /// iroh node ID (32-byte public key) with optional relay info.
    Iroh(Vec<u8>),
    /// IP socket address for TCP/UDP transports.
    Socket(std::net::SocketAddr),
    /// LoRa device address (4 bytes).
    LoRa([u8; 4]),
    /// Serial port identifier.
    Serial(String),
    /// Opaque bytes for unknown/future transports.
    Raw(Vec<u8>),
}

impl fmt::Display for TransportAddr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Iroh(id) => write!(f, "iroh:{}", hex::encode(&id[..4.min(id.len())])),
            Self::Socket(addr) => write!(f, "tcp:{addr}"),
            Self::LoRa(addr) => write!(f, "lora:{}", hex::encode(addr)),
            Self::Serial(port) => write!(f, "serial:{port}"),
            Self::Raw(data) => write!(f, "raw:{}", hex::encode(&data[..4.min(data.len())])),
        }
    }
}

/// Metadata about a transport's capabilities.
#[derive(Clone, Debug)]
pub struct TransportInfo {
    /// Human-readable transport name.
    pub name: String,
    /// Maximum transmission unit in bytes.
    pub mtu: usize,
    /// Estimated bitrate in bits/second.
    pub bitrate: u64,
    /// Whether this transport supports bidirectional communication.
    pub bidirectional: bool,
}

/// Received packet from a transport.
#[derive(Clone, Debug)]
pub struct TransportPacket {
    /// Source address of the sender.
    pub from: TransportAddr,
    /// Raw packet data.
    pub data: Vec<u8>,
}

/// A pluggable mesh transport backend.
///
/// Implementations provide send/receive over a specific medium (QUIC, TCP, LoRa, etc).
#[async_trait::async_trait]
pub trait MeshTransport: Send + Sync {
    /// Transport metadata (name, MTU, bitrate).
    fn info(&self) -> TransportInfo;

    /// Send raw bytes to a destination.
    async fn send(&self, dest: &TransportAddr, data: &[u8]) -> Result<()>;

    /// Receive the next incoming packet. Blocks until data arrives.
    async fn recv(&self) -> Result<TransportPacket>;

    /// Discover reachable peers on this transport.
    /// Returns an empty vec if discovery is not supported.
    async fn discover(&self) -> Result<Vec<TransportAddr>> {
        Ok(Vec::new())
    }

    /// Gracefully shut down this transport.
    async fn close(&self) -> Result<()> {
        Ok(())
    }
}

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

    #[test]
    fn transport_addr_display_iroh() {
        let addr = TransportAddr::Iroh(vec![0xDE, 0xAD, 0xBE, 0xEF, 0x01, 0x02]);
        assert_eq!(addr.to_string(), "iroh:deadbeef");
    }

    #[test]
    fn transport_addr_display_iroh_short() {
        let addr = TransportAddr::Iroh(vec![0xAB, 0xCD]);
        assert_eq!(addr.to_string(), "iroh:abcd");
    }

    #[test]
    fn transport_addr_display_socket() {
        let addr = TransportAddr::Socket("127.0.0.1:9000".parse().unwrap());
        assert_eq!(addr.to_string(), "tcp:127.0.0.1:9000");
    }

    #[test]
    fn transport_addr_display_lora() {
        let addr = TransportAddr::LoRa([0x01, 0x02, 0x03, 0x04]);
        assert_eq!(addr.to_string(), "lora:01020304");
    }

    #[test]
    fn transport_addr_display_serial() {
        let addr = TransportAddr::Serial("/dev/ttyUSB0".to_string());
        assert_eq!(addr.to_string(), "serial:/dev/ttyUSB0");
    }

    #[test]
    fn transport_addr_display_raw() {
        let addr = TransportAddr::Raw(vec![0xFF, 0xEE, 0xDD, 0xCC, 0xBB]);
        assert_eq!(addr.to_string(), "raw:ffeeddcc");
    }

    #[test]
    fn transport_addr_display_raw_short() {
        let addr = TransportAddr::Raw(vec![0x01]);
        assert_eq!(addr.to_string(), "raw:01");
    }

    #[test]
    fn transport_addr_equality() {
        let a = TransportAddr::Socket("127.0.0.1:8080".parse().unwrap());
        let b = TransportAddr::Socket("127.0.0.1:8080".parse().unwrap());
        let c = TransportAddr::Socket("127.0.0.1:9090".parse().unwrap());
        assert_eq!(a, b);
        assert_ne!(a, c);
    }
}
