#![cfg(target_os = "linux")]

mod common;

use std::io::{Read, Write};
use std::os::unix::net::UnixListener as StdUnixListener;
use std::path::PathBuf;
use std::ptr::NonNull;
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};

use dambi::Bytes;
use dope_core::driver::{Backend, DriverConfig};
use dope_futures::conn::{Conn, ConnState};
use dope_transport::Unix;
use dope_uring::{CurrentSlot, UringBackend, set_current};

use common::Drive;

const TICK_BUDGET: Duration = Duration::from_secs(5);

fn tmp_socket_path() -> PathBuf {
    let mut p = std::env::temp_dir();
    p.push(format!(
        "dope-unix-conn-smoke-{}-{}.sock",
        std::process::id(),
        Instant::now().elapsed().as_nanos(),
    ));
    let _ = std::fs::remove_file(&p);
    p
}

#[test]
fn connect_send_recv_roundtrip() {
    let path = tmp_socket_path();
    let listener = StdUnixListener::bind(&path).expect("bind unix listener");
    listener.set_nonblocking(false).unwrap();

    let (server_tx, server_rx) = mpsc::sync_channel::<Vec<u8>>(1);
    let server_path = path.clone();
    let server_thread = thread::spawn(move || {
        let (mut sock, _) = listener.accept().expect("accept");
        sock.set_read_timeout(Some(Duration::from_secs(3))).ok();
        let mut buf = [0u8; 64];
        let n = sock.read(&mut buf).expect("server read");
        server_tx.send(buf[..n].to_vec()).ok();
        sock.write_all(b"echo:hello").expect("server write");
        let _ = server_path;
    });

    let driver_cfg = <UringBackend as Backend>::Config::for_tcp_profile(128, 16, 4096);
    let mut driver = <UringBackend as Backend>::new_driver(driver_cfg).expect("driver");

    let driver_ptr = NonNull::from(&mut driver);
    set_current(Some(CurrentSlot::new(driver_ptr, std::ptr::null())));

    let mut conn = Conn::<Unix>::connect_path(&path, &mut driver, 0).expect("connect");

    Drive::until(&mut conn, &mut driver, TICK_BUDGET, |c| {
        matches!(c.state(), ConnState::Active)
    });
    assert!(matches!(conn.state(), ConnState::Active), "expected Active");

    conn.try_send(Bytes::from_static(b"hello"))
        .expect("try_send");

    let mut received: Vec<u8> = Vec::new();
    Drive::until(&mut conn, &mut driver, TICK_BUDGET, |c| {
        while let Some(chunk) = c.try_recv() {
            received.extend_from_slice(&*chunk);
        }
        received.len() >= b"echo:hello".len()
    });
    assert_eq!(received, b"echo:hello");

    drop(conn);
    set_current(None);

    let server_received = server_rx
        .recv_timeout(Duration::from_secs(2))
        .expect("server msg");
    assert_eq!(server_received, b"hello");

    server_thread.join().ok();
    let _ = std::fs::remove_file(&path);
}

#[test]
fn connect_to_nonexistent_path_errors() {
    let path = PathBuf::from("/tmp/dope-unix-conn-nonexistent-12345.sock");
    let _ = std::fs::remove_file(&path);

    let driver_cfg = <UringBackend as Backend>::Config::for_tcp_profile(64, 16, 4096);
    let mut driver = <UringBackend as Backend>::new_driver(driver_cfg).expect("driver");
    let driver_ptr = NonNull::from(&mut driver);
    set_current(Some(CurrentSlot::new(driver_ptr, std::ptr::null())));

    let mut conn = Conn::<Unix>::connect_path(&path, &mut driver, 0).expect("connect submitted");

    Drive::until(&mut conn, &mut driver, Duration::from_secs(2), |c| {
        matches!(c.state(), ConnState::Error)
    });
    assert!(conn.last_error().is_some());

    drop(conn);
    set_current(None);
}
