use std::time::Duration;

use rand::Rng;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::time;

use crate::test::LOCALHOST_IP;

pub struct DummyTCPServer(TcpListener);

impl DummyTCPServer {
    pub async fn start(port: u16) -> Self {
        let addr = format!("{}:{}", LOCALHOST_IP, port);
        println!("[DUMMY TCP] Listen on {}", addr);
        let listener = TcpListener::bind(addr)
            .await
            .expect("Failed to bind dummy TCP listener!");
        Self(listener)
    }

    /// Receive chunk of data from following connection
    pub async fn read_next_connection(&self) -> Vec<u8> {
        let (mut conn, _addr) = self
            .0
            .accept()
            .await
            .expect("Could not open next connection!");

        let mut buff = Vec::with_capacity(100);
        conn.read_to_end(&mut buff).await.unwrap();

        buff
    }

    /// Receive chunk of data from following connection
    pub async fn read_then_write_next_connection(&self, to_send: &[u8]) -> Vec<u8> {
        let (mut conn, _addr) = self
            .0
            .accept()
            .await
            .expect("Could not open next connection!");

        let mut buff: [u8; 100] = [0; 100];
        let size = conn.read(&mut buff).await.unwrap();

        conn.write_all(to_send).await.unwrap();

        buff[0..size].to_vec()
    }

    /// Receive chunk of data from following connection
    pub async fn write_next_connection(&self, to_send: &[u8]) {
        let (mut conn, _addr) = self
            .0
            .accept()
            .await
            .expect("Could not open next connection!");

        conn.write_all(to_send).await.unwrap()
    }

    /// Perform complex exchange: receive numbers from client and respond with their square
    pub async fn next_conn_square_operations(&self) {
        let (mut conn, _addr) = self
            .0
            .accept()
            .await
            .expect("Could not open next connection!");

        let mut buff: [u8; 100] = [0; 100];
        loop {
            let size = conn.read(&mut buff).await.unwrap();
            if size == 0 {
                break;
            }

            let content = String::from_utf8_lossy(&buff[0..size])
                .to_string()
                .parse::<i64>()
                .unwrap();

            conn.write_all((content * content).to_string().as_bytes())
                .await
                .unwrap();
        }
    }

    pub async fn loop_conn_square_operations(&self) {
        loop {
            self.next_conn_square_operations().await
        }
    }

    /// Perform complex exchange: receive numbers from client and respond with their value + a given number
    pub async fn next_conn_add_operations(&self, inc: i32) {
        let (mut conn, _addr) = self
            .0
            .accept()
            .await
            .expect("Could not open next connection!");

        let mut buff: [u8; 100] = [0; 100];
        loop {
            let size = conn.read(&mut buff).await.unwrap();
            if size == 0 {
                break;
            }

            let content = String::from_utf8_lossy(&buff[0..size])
                .to_string()
                .parse::<i64>()
                .unwrap();

            conn.write_all((content + inc as i64).to_string().as_bytes())
                .await
                .unwrap();
        }
    }

    pub async fn loop_next_conn_add_operations(&self, inc: i32) {
        loop {
            self.next_conn_add_operations(inc).await
        }
    }
}

pub async fn dummy_tcp_client_read_conn(port: u16) -> Vec<u8> {
    let mut socket = TcpStream::connect(format!("127.0.0.1:{}", port))
        .await
        .expect("Failed to connect to dummy TCP server!");

    let mut buff: [u8; 100] = [0; 100];
    let size = socket.read(&mut buff).await.unwrap();

    buff[0..size].to_vec()
}

pub async fn dummy_tcp_client_write_then_read_conn(port: u16, data: &[u8]) -> Vec<u8> {
    let mut socket = TcpStream::connect(format!("127.0.0.1:{}", port))
        .await
        .expect("Failed to connect to dummy TCP server!");

    socket.write_all(data).await.unwrap();

    let mut buff: [u8; 100] = [0; 100];
    let size = socket.read(&mut buff).await.unwrap();

    buff[0..size].to_vec()
}

pub async fn dummy_tcp_client_write_conn(port: u16, data: &[u8]) {
    let mut socket = TcpStream::connect(format!("127.0.0.1:{}", port))
        .await
        .expect("Failed to connect to dummy TCP server!");

    socket.write_all(data).await.unwrap()
}

pub async fn dummy_tcp_client_square_root_requests(port: u16, num_exchanges: usize) {
    let mut socket = TcpStream::connect(format!("127.0.0.1:{}", port))
        .await
        .expect("Failed to connect to dummy TCP server!");

    let mut rng = rand::thread_rng();
    let mut buff: [u8; 100] = [0; 100];

    for _ in 0..num_exchanges {
        let num = rng.gen::<i32>() % 100;
        socket.write_all(num.to_string().as_bytes()).await.unwrap();

        let size = socket.read(&mut buff).await.unwrap();

        if size == 0 {
            panic!("Got empty response!");
        }

        let got = String::from_utf8_lossy(&buff[0..size])
            .to_string()
            .parse::<i64>()
            .unwrap();
        println!("{} * {} = {} (based on server response)", num, num, got);
        assert_eq!((num * num) as i64, got);
    }
}

pub async fn dummy_tcp_client_additions_requests(port: u16, inc: i32, num_exchanges: usize) {
    let mut socket = TcpStream::connect(format!("127.0.0.1:{}", port))
        .await
        .expect("Failed to connect to dummy TCP server!");

    let mut rng = rand::thread_rng();
    let mut buff: [u8; 100] = [0; 100];

    for _ in 0..num_exchanges {
        let num = rng.gen::<i32>() % 100;
        socket.write_all(num.to_string().as_bytes()).await.unwrap();

        let size = socket.read(&mut buff).await.unwrap();

        if size == 0 {
            panic!("Got empty response!");
        }

        let got = String::from_utf8_lossy(&buff[0..size])
            .to_string()
            .parse::<i64>()
            .unwrap();
        println!("{} + {} = {} (based on server response)", num, inc, got);
        assert_eq!((num + inc) as i64, got);
    }
}

/// Check whether a given port is open or not
pub async fn is_port_open(port: u16) -> bool {
    match TcpStream::connect(("127.0.0.1", port)).await {
        Ok(_) => true,
        Err(_) => false,
    }
}

/// Wait for a port to become available
pub async fn wait_for_port(port: u16) {
    for _ in 0..50 {
        if is_port_open(port).await {
            return;
        }
        time::sleep(Duration::from_millis(10)).await;
    }

    eprintln!("Port {} did not open in time!", port);
    std::process::exit(2);
}

mod test {
    use crate::test::dummy_tcp_sockets::{
        dummy_tcp_client_additions_requests, dummy_tcp_client_read_conn,
        dummy_tcp_client_square_root_requests, dummy_tcp_client_write_conn,
        dummy_tcp_client_write_then_read_conn, DummyTCPServer,
    };
    use crate::test::{get_port_number, PortsAllocation};

    fn port(index: u16) -> u16 {
        get_port_number(PortsAllocation::DummyTCPServer, index)
    }

    #[tokio::test]
    async fn socket_read_from_server() {
        const DATA: &[u8] = "Hello world!!!".as_bytes();

        let listener = DummyTCPServer::start(port(0)).await;
        let handle = tokio::spawn(async move {
            listener.write_next_connection(DATA).await;
        });
        let data = dummy_tcp_client_read_conn(port(0)).await;
        assert_eq!(data, DATA);

        handle.await.unwrap();
    }

    #[tokio::test]
    async fn socket_write_to_server() {
        const DATA: &[u8] = "Hello world 2".as_bytes();

        let listener = DummyTCPServer::start(port(1)).await;
        tokio::spawn(async move {
            dummy_tcp_client_write_conn(port(1), DATA).await;
        });
        let data = listener.read_next_connection().await;
        assert_eq!(data, DATA);
    }

    #[tokio::test]
    async fn socket_read_and_write_to_server() {
        const DATA_1: &[u8] = "Hello world 3a".as_bytes();
        const DATA_2: &[u8] = "Hello world 3b".as_bytes();

        let listener = DummyTCPServer::start(port(2)).await;
        let handle = tokio::spawn(async move {
            println!("client handle");
            let data = dummy_tcp_client_write_then_read_conn(port(2), DATA_1).await;
            assert_eq!(data, DATA_2);
        });
        let h2 = tokio::spawn(async move {
            println!("server handle");
            let data = listener.read_then_write_next_connection(DATA_2).await;
            assert_eq!(data, DATA_1);
        });

        handle.await.unwrap();
        h2.await.unwrap();
    }

    #[tokio::test]
    async fn socket_dummy_root_calculator() {
        let listener = DummyTCPServer::start(port(3)).await;
        let handle = tokio::spawn(async move {
            listener.next_conn_square_operations().await;
        });
        let data = dummy_tcp_client_write_then_read_conn(port(3), "5".as_bytes()).await;
        assert_eq!(data, "25".as_bytes());

        handle.await.unwrap();
    }

    #[tokio::test]
    async fn socket_dummy_root_calculator_multiple() {
        let listener = DummyTCPServer::start(port(4)).await;
        let handle = tokio::spawn(async move {
            listener.next_conn_square_operations().await;
        });
        dummy_tcp_client_square_root_requests(port(4), 10).await;

        handle.await.unwrap();
    }

    #[tokio::test]
    async fn socket_dummy_addition_calculator() {
        let listener = DummyTCPServer::start(port(5)).await;
        let handle = tokio::spawn(async move {
            listener.next_conn_add_operations(4).await;
        });
        let data = dummy_tcp_client_write_then_read_conn(port(5), "5".as_bytes()).await;
        assert_eq!(data, "9".as_bytes());

        handle.await.unwrap();
    }

    #[tokio::test]
    async fn socket_dummy_addition_calculator_multiple() {
        let listener = DummyTCPServer::start(port(6)).await;
        let handle = tokio::spawn(async move {
            listener.next_conn_add_operations(7).await;
        });
        dummy_tcp_client_additions_requests(port(6), 7, 10).await;

        handle.await.unwrap();
    }
}