//! End-to-end control-plane test: boots the full sidecar (UDP + control socket
//! + SFU loop) in-process and speaks the JSON-lines protocol like PHP does.

use std::io::{BufRead, BufReader, Write};
use std::net::IpAddr;
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::thread;
use std::time::{Duration, Instant};

use blax_webrtc_sfu::server::{self, Config};

fn start_sidecar(socket: PathBuf) -> thread::JoinHandle<()> {
    let config = Config {
        socket,
        udp_ip: IpAddr::from([127, 0, 0, 1]),
        udp_port: 0,
        public_ip: None,
    };
    thread::spawn(move || server::run(config).expect("sidecar to run"))
}

fn connect(socket: &PathBuf) -> UnixStream {
    let deadline = Instant::now() + Duration::from_secs(5);
    loop {
        if let Ok(stream) = UnixStream::connect(socket) {
            return stream;
        }
        assert!(Instant::now() < deadline, "control socket never came up");
        thread::sleep(Duration::from_millis(20));
    }
}

fn roundtrip(reader: &mut impl BufRead, writer: &mut impl Write, req: &str) -> serde_json::Value {
    writeln!(writer, "{req}").unwrap();
    writer.flush().unwrap();
    let mut line = String::new();
    reader.read_line(&mut line).unwrap();
    serde_json::from_str(&line).unwrap_or_else(|e| panic!("bad reply {line:?}: {e}"))
}

#[test]
fn control_protocol_end_to_end() {
    let socket = std::env::temp_dir().join(format!("blax-sfu-test-{}.sock", std::process::id()));
    let _ = std::fs::remove_file(&socket);
    let sidecar = start_sidecar(socket.clone());

    let stream = connect(&socket);
    let mut reader = BufReader::new(stream.try_clone().unwrap());
    let mut writer = stream;

    // ping
    let reply = roundtrip(&mut reader, &mut writer, r#"{"id":1,"cmd":"ping"}"#);
    assert_eq!(reply["id"], 1);
    assert_eq!(reply["ok"], true);

    // empty stats
    let reply = roundtrip(&mut reader, &mut writer, r#"{"id":2,"cmd":"stats"}"#);
    assert_eq!(reply["ok"], true);
    assert_eq!(reply["rooms"], serde_json::json!([]));

    // add_peer with a garbage offer errors cleanly
    let reply = roundtrip(
        &mut reader,
        &mut writer,
        r#"{"id":3,"cmd":"add_peer","room":"r1","peer":"p1","offer":"not sdp"}"#,
    );
    assert_eq!(reply["ok"], false);
    assert!(
        reply["error"].as_str().unwrap().contains("offer"),
        "unexpected error: {reply}"
    );

    // unknown peer errors cleanly
    let reply = roundtrip(
        &mut reader,
        &mut writer,
        r#"{"id":4,"cmd":"mute_peer","room":"r1","peer":"ghost","muted":true}"#,
    );
    assert_eq!(reply["ok"], false);
    assert!(reply["error"].as_str().unwrap().contains("no such peer"));

    // malformed json → error with id 0, connection stays usable
    let reply = roundtrip(&mut reader, &mut writer, r#"{"id":5,"cmd":"nope"}"#);
    assert_eq!(reply["ok"], false);

    let reply = roundtrip(&mut reader, &mut writer, r#"{"id":6,"cmd":"ping"}"#);
    assert_eq!(reply["ok"], true);

    // shutdown stops the loop and removes the socket file
    let reply = roundtrip(&mut reader, &mut writer, r#"{"id":7,"cmd":"shutdown"}"#);
    assert_eq!(reply["ok"], true);

    sidecar.join().expect("sidecar thread to finish");
    assert!(!socket.exists(), "socket file cleaned up on shutdown");
}
