//! TCP client interface (`ARCHITECTURE.md` §4.2, priority #1).
//!
//! Connects to a Reticulum TCP server (e.g. a Python `rnsd`'s
//! `TCPServerInterface`), HDLC-frames outbound packets, and deframes the inbound
//! byte stream. Speaks `mpsc<TxFrame>`/`mpsc<RxFrame>` to the gateway.

use async_trait::async_trait;
use bytes::Bytes;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::{broadcast, mpsc};
use tokio_util::sync::CancellationToken;

use plexus_gateway::InterfaceId;

use crate::{hdlc, Error, Interface, InterfaceHandle, InterfaceKind, RxFrame, TxFrame};

/// Reticulum link MTU.
const MTU: usize = 500;
/// Bounded channel depth between the gateway and this interface.
const CHANNEL_DEPTH: usize = 256;
/// Socket read buffer size.
const READ_BUF: usize = 8192;

/// A TCP client interface that dials a remote Reticulum TCP server.
pub struct TcpClient {
    id: InterfaceId,
    addr: String,
}

impl TcpClient {
    /// Create a TCP client for interface `id` dialing `addr` (`host:port`).
    #[must_use]
    pub fn new(id: InterfaceId, addr: impl Into<String>) -> Self {
        Self {
            id,
            addr: addr.into(),
        }
    }
}

#[async_trait]
impl Interface for TcpClient {
    fn id(&self) -> InterfaceId {
        self.id
    }

    fn kind(&self) -> InterfaceKind {
        InterfaceKind::Tcp
    }

    fn mtu(&self) -> usize {
        MTU
    }

    async fn start(self: Box<Self>) -> Result<InterfaceHandle, Error> {
        let stream = TcpStream::connect(&self.addr).await?;
        stream.set_nodelay(true).ok();
        let (mut read_half, mut write_half) = stream.into_split();

        // gateway → interface (we write these to the socket)
        let (to_iface_tx, mut to_iface_rx) = mpsc::channel::<TxFrame>(CHANNEL_DEPTH);
        // interface → gateway (frames we deframe off the socket)
        let (from_iface_tx, from_iface_rx) = mpsc::channel::<RxFrame>(CHANNEL_DEPTH);
        let cancel = CancellationToken::new();

        // Writer task: HDLC-frame outbound packets and write them.
        let write_cancel = cancel.clone();
        tokio::spawn(async move {
            loop {
                tokio::select! {
                    () = write_cancel.cancelled() => break,
                    msg = to_iface_rx.recv() => {
                        let Some(frame) = msg else { break };
                        if write_half.write_all(&hdlc::frame(&frame.bytes)).await.is_err() {
                            break;
                        }
                    }
                }
            }
        });

        // Reader task: deframe the inbound stream into RxFrames.
        let read_cancel = cancel.clone();
        tokio::spawn(async move {
            let mut deframer = hdlc::Deframer::new();
            let mut buf = [0u8; READ_BUF];
            let mut frames: Vec<Vec<u8>> = Vec::new();
            loop {
                tokio::select! {
                    () = read_cancel.cancelled() => break,
                    res = read_half.read(&mut buf) => {
                        match res {
                            Ok(0) | Err(_) => break, // EOF or error
                            Ok(n) => {
                                frames.clear();
                                deframer.push(&buf[..n], &mut frames);
                                for raw in frames.drain(..) {
                                    let rx = RxFrame { bytes: Bytes::from(raw), rssi: None, snr: None };
                                    if from_iface_tx.send(rx).await.is_err() {
                                        return;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        });

        Ok(InterfaceHandle {
            id: self.id,
            tx: to_iface_tx,
            rx: from_iface_rx,
            cancel,
        })
    }
}

/// A TCP server interface: accepts inbound Reticulum TCP connections, deframes
/// each into `RxFrame`s for the gateway, and HDLC-frames outbound packets to
/// **all** connected peers (broadcast fan-out, as a Reticulum transport node).
pub struct TcpServer {
    id: InterfaceId,
    bind: String,
}

impl TcpServer {
    /// Create a TCP server interface for `id` listening on `bind` (`host:port`).
    #[must_use]
    pub fn new(id: InterfaceId, bind: impl Into<String>) -> Self {
        Self {
            id,
            bind: bind.into(),
        }
    }
}

#[async_trait]
impl Interface for TcpServer {
    fn id(&self) -> InterfaceId {
        self.id
    }

    fn kind(&self) -> InterfaceKind {
        InterfaceKind::Tcp
    }

    fn mtu(&self) -> usize {
        MTU
    }

    async fn start(self: Box<Self>) -> Result<InterfaceHandle, Error> {
        let listener = TcpListener::bind(&self.bind).await?;

        let (to_iface_tx, mut to_iface_rx) = mpsc::channel::<TxFrame>(CHANNEL_DEPTH);
        let (from_iface_tx, from_iface_rx) = mpsc::channel::<RxFrame>(CHANNEL_DEPTH);
        let cancel = CancellationToken::new();
        // Framed outbound packets, fanned out to every connected peer.
        let (out_tx, _) = broadcast::channel::<Bytes>(CHANNEL_DEPTH);

        // Outbound pump: frame gateway packets, broadcast to all peer writers.
        let pump_out = out_tx.clone();
        let pump_cancel = cancel.clone();
        tokio::spawn(async move {
            loop {
                tokio::select! {
                    () = pump_cancel.cancelled() => break,
                    msg = to_iface_rx.recv() => {
                        let Some(frame) = msg else { break };
                        let _ = pump_out.send(Bytes::from(hdlc::frame(&frame.bytes)));
                    }
                }
            }
        });

        // Accept loop: per connection spawn a reader and a writer.
        let accept_cancel = cancel.clone();
        tokio::spawn(async move {
            loop {
                tokio::select! {
                    () = accept_cancel.cancelled() => break,
                    accepted = listener.accept() => {
                        let Ok((stream, _peer)) = accepted else { continue };
                        stream.set_nodelay(true).ok();
                        let (mut read_half, mut write_half) = stream.into_split();

                        // Reader: deframe inbound stream into RxFrames.
                        let rx_tx = from_iface_tx.clone();
                        let reader_cancel = accept_cancel.clone();
                        tokio::spawn(async move {
                            let mut deframer = hdlc::Deframer::new();
                            let mut buf = [0u8; READ_BUF];
                            let mut frames: Vec<Vec<u8>> = Vec::new();
                            loop {
                                tokio::select! {
                                    () = reader_cancel.cancelled() => break,
                                    res = read_half.read(&mut buf) => match res {
                                        Ok(0) | Err(_) => break,
                                        Ok(n) => {
                                            frames.clear();
                                            deframer.push(&buf[..n], &mut frames);
                                            for raw in frames.drain(..) {
                                                let rx = RxFrame { bytes: Bytes::from(raw), rssi: None, snr: None };
                                                if rx_tx.send(rx).await.is_err() {
                                                    return;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        });

                        // Writer: write each broadcast frame to this peer.
                        let mut out_rx = out_tx.subscribe();
                        let writer_cancel = accept_cancel.clone();
                        tokio::spawn(async move {
                            loop {
                                tokio::select! {
                                    () = writer_cancel.cancelled() => break,
                                    msg = out_rx.recv() => match msg {
                                        Ok(framed) => {
                                            if write_half.write_all(&framed).await.is_err() {
                                                break;
                                            }
                                        }
                                        Err(broadcast::error::RecvError::Lagged(_)) => {}
                                        Err(broadcast::error::RecvError::Closed) => break,
                                    }
                                }
                            }
                        });
                    }
                }
            }
        });

        Ok(InterfaceHandle {
            id: self.id,
            tx: to_iface_tx,
            rx: from_iface_rx,
            cancel,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::{TcpClient, TcpServer};
    use crate::{hdlc, Interface, Priority, TxFrame};
    use bytes::Bytes;
    use plexus_gateway::InterfaceId;
    use std::time::Duration;
    use tokio::io::AsyncWriteExt;
    use tokio::net::TcpListener;

    #[tokio::test]
    async fn receives_a_framed_packet_over_tcp() {
        let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
        let addr = listener.local_addr().unwrap();
        let payload = b"plexus interop loopback payload".to_vec();

        let server_payload = payload.clone();
        tokio::spawn(async move {
            let (mut sock, _) = listener.accept().await.unwrap();
            sock.write_all(&hdlc::frame(&server_payload)).await.unwrap();
            sock.flush().await.unwrap();
            tokio::time::sleep(Duration::from_millis(200)).await;
        });

        let client = Box::new(TcpClient::new(InterfaceId(7), addr.to_string()));
        let mut handle = client.start().await.unwrap();

        let rx = tokio::time::timeout(Duration::from_secs(3), handle.rx.recv())
            .await
            .expect("timed out waiting for frame")
            .expect("channel closed");
        assert_eq!(&rx.bytes[..], &payload[..]);
        handle.cancel.cancel();
    }

    #[tokio::test]
    async fn server_and_client_exchange_frames() {
        let port = std::net::TcpListener::bind("127.0.0.1:0")
            .unwrap()
            .local_addr()
            .unwrap()
            .port();

        let server = Box::new(TcpServer::new(InterfaceId(1), format!("127.0.0.1:{port}")));
        let mut server_handle = server.start().await.unwrap();
        tokio::time::sleep(Duration::from_millis(50)).await;

        let client = Box::new(TcpClient::new(InterfaceId(2), format!("127.0.0.1:{port}")));
        let mut client_handle = client.start().await.unwrap();
        // Let the server accept the connection and subscribe its writer.
        tokio::time::sleep(Duration::from_millis(100)).await;

        // gateway → server interface → peer client
        server_handle
            .tx
            .send(TxFrame {
                bytes: Bytes::from_static(b"downstream"),
                priority: Priority::Normal,
            })
            .await
            .unwrap();
        let down = tokio::time::timeout(Duration::from_secs(3), client_handle.rx.recv())
            .await
            .expect("timed out (server->client)")
            .expect("channel closed");
        assert_eq!(&down.bytes[..], b"downstream");

        // peer client → server interface → gateway
        client_handle
            .tx
            .send(TxFrame {
                bytes: Bytes::from_static(b"upstream"),
                priority: Priority::Normal,
            })
            .await
            .unwrap();
        let up = tokio::time::timeout(Duration::from_secs(3), server_handle.rx.recv())
            .await
            .expect("timed out (client->server)")
            .expect("channel closed");
        assert_eq!(&up.bytes[..], b"upstream");

        server_handle.cancel.cancel();
        client_handle.cancel.cancel();
    }
}
