//! Multi-transport manager for routing packets across different backends.
//!
//! The [`TransportManager`] holds multiple [`MeshTransport`] implementations
//! and selects the best one for a given [`TransportAddr`] variant.
//!
//! [`crate::transport_lora::LoRaTransport`] performs MTU-aware fragmentation internally; use
//! [`crate::transport_lora::split_for_mtu`] only when chunking at a higher layer.

use anyhow::{bail, Result};

use crate::transport::{MeshTransport, TransportAddr, TransportInfo};

/// Manages multiple mesh transports and routes packets to the best available one.
pub struct TransportManager {
    transports: Vec<Box<dyn MeshTransport>>,
}

impl TransportManager {
    /// Create an empty transport manager.
    pub fn new() -> Self {
        Self {
            transports: Vec::new(),
        }
    }

    /// Register a transport backend.
    pub fn add(&mut self, transport: Box<dyn MeshTransport>) {
        self.transports.push(transport);
    }

    /// Send data, choosing the best transport for the given address type.
    ///
    /// The selection heuristic matches the [`TransportAddr`] variant to the
    /// transport whose name corresponds to that variant (iroh for `Iroh`,
    /// tcp for `Socket`, etc). Falls back to trying each transport in order.
    pub async fn send(&self, dest: &TransportAddr, data: &[u8]) -> Result<()> {
        let target_name = match dest {
            TransportAddr::Iroh(_) => "iroh-quic",
            TransportAddr::Socket(_) => "tcp",
            TransportAddr::LoRa(_) => "lora",
            TransportAddr::Serial(_) => "serial",
            TransportAddr::Raw(_) => "",
        };

        // First, try the transport whose name matches the address type.
        for t in &self.transports {
            if t.info().name == target_name {
                return t.send(dest, data).await;
            }
        }

        // Fallback: try each transport in order until one succeeds.
        let mut last_err = None;
        for t in &self.transports {
            match t.send(dest, data).await {
                Ok(()) => return Ok(()),
                Err(e) => last_err = Some(e),
            }
        }

        match last_err {
            Some(e) => Err(e),
            None => bail!("no transports registered"),
        }
    }

    /// List all registered transports.
    pub fn transports(&self) -> &[Box<dyn MeshTransport>] {
        &self.transports
    }

    /// Get info for all registered transports.
    pub fn transport_info(&self) -> Vec<TransportInfo> {
        self.transports.iter().map(|t| t.info()).collect()
    }

    /// Shut down all transports.
    pub async fn close_all(&self) -> Result<()> {
        for t in &self.transports {
            t.close().await?;
        }
        Ok(())
    }
}

impl Default for TransportManager {
    fn default() -> Self {
        Self::new()
    }
}

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

    /// A mock transport that accepts any send and returns a fixed name.
    struct MockTransport {
        name: String,
    }

    impl MockTransport {
        fn new(name: &str) -> Self {
            Self {
                name: name.to_string(),
            }
        }
    }

    #[async_trait::async_trait]
    impl MeshTransport for MockTransport {
        fn info(&self) -> TransportInfo {
            TransportInfo {
                name: self.name.clone(),
                mtu: 1500,
                bitrate: 1_000_000,
                bidirectional: true,
            }
        }

        async fn send(&self, _dest: &TransportAddr, _data: &[u8]) -> Result<()> {
            Ok(())
        }

        async fn recv(&self) -> Result<TransportPacket> {
            bail!("MockTransport does not support recv")
        }
    }

    #[tokio::test]
    async fn routes_socket_to_tcp() {
        let mut mgr = TransportManager::new();
        mgr.add(Box::new(MockTransport::new("tcp")));
        mgr.add(Box::new(MockTransport::new("iroh-quic")));

        let addr = TransportAddr::Socket("127.0.0.1:8080".parse().unwrap());
        let result = mgr.send(&addr, b"test data").await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn routes_iroh_to_iroh_transport() {
        let mut mgr = TransportManager::new();
        mgr.add(Box::new(MockTransport::new("tcp")));
        mgr.add(Box::new(MockTransport::new("iroh-quic")));

        let addr = TransportAddr::Iroh(vec![0xAA; 32]);
        let result = mgr.send(&addr, b"test data").await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn no_transports_returns_error() {
        let mgr = TransportManager::new();
        let addr = TransportAddr::Socket("127.0.0.1:8080".parse().unwrap());
        let result = mgr.send(&addr, b"data").await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn transport_info_lists_all() {
        let mut mgr = TransportManager::new();
        mgr.add(Box::new(MockTransport::new("tcp")));
        mgr.add(Box::new(MockTransport::new("iroh-quic")));

        let infos = mgr.transport_info();
        assert_eq!(infos.len(), 2);
        assert_eq!(infos[0].name, "tcp");
        assert_eq!(infos[1].name, "iroh-quic");
    }

    #[tokio::test]
    async fn close_all_succeeds() {
        let mut mgr = TransportManager::new();
        mgr.add(Box::new(MockTransport::new("tcp")));
        mgr.add(Box::new(MockTransport::new("iroh-quic")));

        let result = mgr.close_all().await;
        assert!(result.is_ok());
    }
}
