use std::time::Duration;

use dope_futures::listener::Listener;
use dope_futures::stream::Stream;
use dope_runtime::{DefaultBackend, block_on};
use dope_transport::Tcp;
use dope_udp::udp_socket::UdpSocket;

#[test]
fn tcp_accept_connect_smoke() {
    block_on(async {
        let mut listener = Listener::<Tcp, DefaultBackend>::bind("127.0.0.1:0").expect("bind");
        let addr = listener.local_addr().expect("local_addr");

        let server = dope_runtime::spawn(async move {
            let (stream, peer) = listener.accept().await.expect("accept");
            drop(stream);
            peer
        });

        let client = match Stream::<Tcp, DefaultBackend>::connect_with(addr).await {
            Ok(stream) => stream,
            Err(err)
                if matches!(
                    err.raw_os_error(),
                    Some(libc::EAFNOSUPPORT)
                        | Some(libc::EPROTONOSUPPORT)
                        | Some(libc::EINVAL)
                        | Some(libc::ENOSYS)
                ) =>
            {
                return;
            }
            Err(err) => panic!("connect failed: {err}"),
        };
        drop(client);

        let accepted_peer = dope_runtime::timeout(Duration::from_secs(1), server)
            .await
            .expect("join");
        assert_eq!(
            accepted_peer.ip(),
            std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)
        );
        assert_ne!(accepted_peer.port(), 0);
    });
}

#[test]
fn udp_send_recv_smoke() {
    block_on(async {
        let a = UdpSocket::<DefaultBackend>::bind("127.0.0.1:0").expect("bind a");
        let b = UdpSocket::<DefaultBackend>::bind("127.0.0.1:0").expect("bind b");
        let b_addr = b.local_addr().expect("b addr");

        match a.send_to(b"ping".to_vec(), b_addr).await {
            Ok(written) => assert_eq!(written, 4),
            Err(err)
                if matches!(
                    err.raw_os_error(),
                    Some(libc::EAFNOSUPPORT)
                        | Some(libc::EPROTONOSUPPORT)
                        | Some(libc::EINVAL)
                        | Some(libc::ENOSYS)
                ) =>
            {
                return;
            }
            Err(err) => panic!("send_to failed: {err}"),
        }

        let (recv, from) = b.recv_from(64).await.expect("recv_from");
        assert_eq!(recv, b"ping");
        assert_eq!(from, a.local_addr().expect("a addr"));
    });
}
