//! Live interop: a real Python `rnsd` accepts an announce *we* originate.
//!
//! Ignored by default (CI has no Python RNS). To run it:
//!
//! ```sh
//! PLEXUS_RNS_PYTHON=$PWD/.venv/bin/python \
//!   cargo test -p plexus-iface --test interop_emit -- --ignored --nocapture
//! ```
//!
//! We connect to `announce_server.py`, register a local destination
//! `plexus.emit`, emit a signed announce, and send the packet over TCP. The
//! server has an announce handler for `plexus.emit`; RNS validates the signature
//! before invoking it, so a `RECV <our-dest>` line proves the reference accepted
//! our announce.

use std::io::BufRead;
use std::process::{Command, Stdio};
use std::time::Duration;

use plexus_gateway::destination::DestinationName;
use plexus_gateway::identity::Identity;
use plexus_gateway::{Gateway, GatewayCmd, GatewayConfig, GatewayEvent, InterfaceId};
use plexus_iface::{Interface, Priority, TcpClient, TxFrame};

#[tokio::test]
#[ignore = "requires a Python RNS reference; set PLEXUS_RNS_PYTHON and run with --ignored"]
async fn python_rnsd_accepts_our_announce() {
    let Ok(python) = std::env::var("PLEXUS_RNS_PYTHON") else {
        eprintln!("PLEXUS_RNS_PYTHON unset; skipping live interop test");
        return;
    };
    let script = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/../plexus-vectors/interop/announce_server.py"
    );
    let port = std::net::TcpListener::bind("127.0.0.1:0")
        .unwrap()
        .local_addr()
        .unwrap()
        .port();

    let mut child = Command::new(&python)
        .arg(script)
        .arg(port.to_string())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()
        .expect("spawn python rnsd");

    let mut reader = std::io::BufReader::new(child.stdout.take().unwrap());
    let mut line = String::new();
    reader.read_line(&mut line).expect("read DEST line"); // server is up once it prints DEST

    // Our node: register the destination + interface, then emit announces.
    let client = Box::new(TcpClient::new(InterfaceId(1), format!("127.0.0.1:{port}")));
    let handle = client.start().await.expect("connect to python rnsd");
    let mut gateway = Gateway::new(
        Identity::from_bytes(&[5u8; 64]).unwrap(),
        GatewayConfig::default(),
    );
    let our_dest = gateway.register_destination(DestinationName::new("plexus", &["emit"]));
    gateway.register_interface(InterfaceId(1));
    let our_dest_hex = hex_encode(our_dest.as_bytes());

    // Read the server's stdout on a blocking thread, looking for our RECV line.
    let (found_tx, found_rx) = std::sync::mpsc::channel::<bool>();
    let want = our_dest_hex.clone();
    std::thread::spawn(move || {
        for line in reader.lines() {
            let Ok(line) = line else { break };
            if let Some(rest) = line.strip_prefix("RECV ") {
                if rest.split_whitespace().next() == Some(want.as_str()) {
                    let _ = found_tx.send(true);
                    return;
                }
            }
        }
    });

    // Emit a few announces (the handler should fire on the first valid one).
    for _ in 0..5 {
        for ev in gateway.step(GatewayCmd::AnnounceLocal {
            destination: our_dest,
            app_data: bytes::Bytes::from_static(b"plexus"),
        }) {
            if let GatewayEvent::PacketOut { bytes, .. } = ev {
                handle
                    .tx
                    .send(TxFrame {
                        bytes,
                        priority: Priority::Normal,
                    })
                    .await
                    .expect("send to interface");
            }
        }
        tokio::time::sleep(Duration::from_millis(300)).await;
    }

    let found = tokio::task::spawn_blocking(move || {
        found_rx
            .recv_timeout(Duration::from_secs(10))
            .unwrap_or(false)
    })
    .await
    .unwrap_or(false);

    handle.cancel.cancel();
    let _ = child.kill();
    let _ = child.wait();

    assert!(
        found,
        "Python rnsd did not accept our announce for {our_dest_hex}"
    );
}

fn hex_encode(bytes: &[u8]) -> String {
    use std::fmt::Write;
    bytes.iter().fold(String::new(), |mut s, b| {
        let _ = write!(s, "{b:02x}");
        s
    })
}
