use std::sync::{Arc, Mutex, Once};
use std::time::Duration;

use nwep::merkle::{LogStorage, MerkleEntry, MerkleEntryType, MerkleLog};
use nwep::server::{ConnInfo, NotifyOptions, Request, ResponseWriter, Router, Settings as ServerSettings};
use nwep::client::ClientBuilder;
use nwep::*;

// ─── Init ────────────────────────────────────────────────────────────────────

static INIT: Once = Once::new();
fn init() {
    INIT.call_once(|| {
        nwep::init().expect("nwep::init failed");
        nwep::log::set_level(nwep::log::LogLevel::Warn);
    });
}

// ─── In-memory log storage ───────────────────────────────────────────────────

struct MemLog(Vec<Vec<u8>>);

impl LogStorage for MemLog {
    fn append(&mut self, _index: u64, entry: &[u8]) -> Result<(), Error> {
        self.0.push(entry.to_vec());
        Ok(())
    }
    fn get(&self, index: u64, buf: &mut [u8]) -> Result<usize, Error> {
        let data = &self.0[index as usize];
        let n = data.len().min(buf.len());
        buf[..n].copy_from_slice(&data[..n]);
        Ok(n)
    }
    fn size(&self) -> u64 { self.0.len() as u64 }
}

// ─── Test server helper ──────────────────────────────────────────────────────

fn now_ns() -> u64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos() as u64
}

/// Starts a server on a random port. Returns (Server, base_url_without_path).
fn start<H: server::Handler>(handler: H) -> (Server, String) {
    start_with_builder(ServerBuilder::new("[::]:0", Keypair::generate().unwrap()), handler)
}

fn start_with_builder<H: server::Handler>(builder: ServerBuilder, handler: H) -> (Server, String) {
    let (srv, el) = builder.build(handler).expect("build server");
    // Use "/" as sentinel path; callers replace it with the actual path.
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });
    (srv, base)
}

fn connect(url: &str) -> Client {
    let kp = Keypair::generate().unwrap();
    ClientBuilder::new()
        .connect(kp, url)
        .expect("connect")
}

/// Parses a u64 from either a plain decimal string ("42") or a JSON object
/// with a numeric field like {"size":42} or {"count":42}.
fn parse_u64_response(body: &[u8]) -> Option<u64> {
    let s = std::str::from_utf8(body).ok()?.trim();
    // Try plain number first
    if let Ok(n) = s.parse::<u64>() {
        return Some(n);
    }
    // Try JSON {"key":N} format
    let inner = s.trim_start_matches('{').trim_end_matches('}');
    if let Some(colon) = inner.rfind(':') {
        let val = inner[colon + 1..].trim();
        if let Ok(n) = val.parse::<u64>() {
            return Some(n);
        }
    }
    None
}

// ─── 1. Basic GET / POST / 404 ───────────────────────────────────────────────

#[test]
fn test_basic_get() {
    init();
    let mut router = Router::new();
    router.handle_func("/hello", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"hello from nwep-rust");
    });
    let (srv, base) = start(router);

    let client = connect(&format!("{base}/hello"));
    let resp = client.get("/hello").unwrap();
    assert_eq!(resp.status, "ok");
    assert_eq!(resp.body, b"hello from nwep-rust");

    client.close();
    srv.shutdown();
}

#[test]
fn test_post_echo_body() {
    init();
    let mut router = Router::new();
    router.handle_func("/echo", |w: &mut ResponseWriter, r: &Request| {
        let _ = w.respond("ok", &r.body);
    });
    let (srv, base) = start(router);

    let client = connect(&format!("{base}/echo"));
    let body = b"echo this back please";
    let resp = client.post("/echo", body).unwrap();
    assert_eq!(resp.status, "ok");
    assert_eq!(resp.body, body);

    client.close();
    srv.shutdown();
}

#[test]
fn test_not_found() {
    init();
    let (srv, base) = start(Router::new());
    let client = connect(&format!("{base}/"));
    let resp = client.get("/no-such-path").unwrap();
    assert!(!nwep::protocol::status_is_success(&resp.status));

    client.close();
    srv.shutdown();
}

// ─── 2. Custom request headers ───────────────────────────────────────────────

#[test]
fn test_request_headers() {
    init();
    let mut router = Router::new();
    router.handle_func("/hdr", |w: &mut ResponseWriter, r: &Request| {
        let val = r.header("x-custom").unwrap_or("missing").to_string();
        let _ = w.respond("ok", val.as_bytes());
    });
    let (srv, base) = start(router);

    let client = connect(&format!("{base}/hdr"));
    let hdrs = [Header::new("x-custom", "myvalue")];
    let resp = client.fetch_with_headers(nwep::protocol::METHOD_READ, "/hdr", b"", &hdrs).unwrap();
    assert_eq!(resp.status, "ok");
    assert_eq!(resp.body, b"myvalue");

    client.close();
    srv.shutdown();
}

// ─── 3. Response headers ─────────────────────────────────────────────────────

#[test]
fn test_response_headers() {
    init();
    let mut router = Router::new();
    router.handle_func("/rsp-hdr", |w: &mut ResponseWriter, _r: &Request| {
        w.set_header("x-response", "yes");
        let _ = w.respond("ok", b"");
    });
    let (srv, base) = start(router);

    let client = connect(&format!("{base}/rsp-hdr"));
    let resp = client.get("/rsp-hdr").unwrap();
    assert_eq!(resp.header("x-response"), Some("yes"));

    client.close();
    srv.shutdown();
}

// ─── 4. Peer identity ────────────────────────────────────────────────────────

#[test]
fn test_peer_identity() {
    init();
    let server_kp = Keypair::generate().unwrap();
    let server_node_id = server_kp.node_id().unwrap();

    let mut router = Router::new();
    router.handle_func("/id", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"");
    });
    let (srv, _el) = ServerBuilder::new("[::]:0", server_kp).build(router).unwrap();
    let url = srv.url("/id");
    std::thread::spawn(move || { let _ = _el.run(); });

    let client_kp = Keypair::generate().unwrap();
    let client_node_id = client_kp.node_id().unwrap();
    let client = ClientBuilder::new().connect(client_kp, &url).unwrap();

    // Client knows the server's node_id (encoded in the URL it connected to)
    let peer_nid = client.peer_node_id();
    assert_eq!(peer_nid, server_node_id);
    // The client's own node_id matches what we generated
    assert_eq!(client.node_id(), client_node_id);

    client.close();
    srv.shutdown();
}

// ─── 5. connection_count / connected_peers ────────────────────────────────────

#[test]
fn test_connection_count_and_peers() {
    init();
    let mut router = Router::new();
    router.handle_func("/ping", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"pong");
    });
    let (srv, base) = start(router);

    assert_eq!(srv.connection_count(), 0);

    let c1 = connect(&format!("{base}/ping"));
    // After connect() returns the server's on_connect has already fired
    assert_eq!(srv.connection_count(), 1);

    let c2 = connect(&format!("{base}/ping"));
    assert_eq!(srv.connection_count(), 2);

    let peers = srv.connected_peers();
    assert_eq!(peers.len(), 2);
    // peers contains the CLIENT node_ids as seen by the server
    assert!(peers.contains(&c1.node_id()) && peers.contains(&c2.node_id()));

    // Use server-side close so the server fires on_disconnect via the QUIC
    // CONNECTION_CLOSE timer (~279ms), which removes c1 from the peer list.
    // Client-side close does not send a CONNECTION_CLOSE packet, so the server
    // would not learn of the disconnect until the 30-second idle timeout.
    srv.close_connection(c1.node_id(), 0).unwrap();
    std::thread::sleep(Duration::from_millis(500));
    assert_eq!(srv.connection_count(), 1);

    c1.close();
    c2.close();
    srv.shutdown();
}

// ─── 6. on_connect / on_disconnect callbacks ─────────────────────────────────

#[test]
fn test_on_connect_disconnect_callbacks() {
    init();
    let connects: Arc<Mutex<Vec<NodeId>>> = Arc::new(Mutex::new(vec![]));
    let disconnects: Arc<Mutex<Vec<(NodeId, i32)>>> = Arc::new(Mutex::new(vec![]));
    let cc = connects.clone();
    let dc = disconnects.clone();

    let mut router = Router::new();
    router.handle_func("/cb", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"");
    });
    let kp = Keypair::generate().unwrap();
    let (srv, el) = ServerBuilder::new("[::]:0", kp)
        .on_connect(move |info: ConnInfo| { cc.lock().unwrap().push(info.node_id); })
        .on_disconnect(move |info: ConnInfo, code: i32| { dc.lock().unwrap().push((info.node_id, code)); })
        .build(router)
        .unwrap();
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });

    let client = connect(&format!("{base}/cb"));
    let client_nid = client.node_id();
    std::thread::sleep(Duration::from_millis(50));
    assert!(connects.lock().unwrap().contains(&client_nid));

    // Close from the server side: this is reliable because CloseConn eagerly fires
    // the disconnect callback without waiting for the C library's idle timeout.
    srv.close_connection(client_nid, 0).unwrap();
    std::thread::sleep(Duration::from_millis(50));
    assert!(disconnects.lock().unwrap().iter().any(|(n, _)| *n == client_nid));

    client.close();

    srv.shutdown();
}

// ─── 7. Concurrent requests ──────────────────────────────────────────────────

#[test]
fn test_concurrent_requests() {
    init();
    let mut router = Router::new();
    router.handle_func("/concurrent", |w: &mut ResponseWriter, r: &Request| {
        let _ = w.respond("ok", &r.body);
    });
    let (srv, base) = start(router);

    // Client is not Sync, so each thread creates its own connection
    let base = Arc::new(base);
    let mut handles = vec![];
    for i in 0..5u8 {
        let b = base.clone();
        handles.push(std::thread::spawn(move || {
            let client = connect(&format!("{b}/concurrent"));
            let body = vec![i; 16];
            let resp = client.post("/concurrent", &body).unwrap();
            assert_eq!(resp.body, body);
            client.close();
        }));
    }
    for h in handles { h.join().unwrap(); }

    srv.shutdown();
}

// ─── 8. Large body ───────────────────────────────────────────────────────────

#[test]
fn test_large_body() {
    init();
    let mut router = Router::new();
    router.handle_func("/large", |w: &mut ResponseWriter, r: &Request| {
        let _ = w.respond("ok", &r.body);
    });
    let (srv, base) = start(router);

    let client = connect(&format!("{base}/large"));
    let big = vec![0x42u8; 64 * 1024]; // 64 KB
    let resp = client.post("/large", &big).unwrap();
    assert_eq!(resp.status, "ok");
    assert_eq!(resp.body.len(), big.len());
    assert_eq!(resp.body, big);

    client.close();
    srv.shutdown();
}

// ─── 9. Prefix routing ───────────────────────────────────────────────────────

#[test]
fn test_prefix_routing() {
    init();
    let mut router = Router::new();
    router.handle_prefix("/api", |w: &mut ResponseWriter, r: &Request| {
        let _ = w.respond("ok", r.path.as_bytes());
    });
    router.handle_func("/other", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"other");
    });
    let (srv, base) = start(router);

    let client = connect(&format!("{base}/api/users"));
    let r1 = client.get("/api/users").unwrap();
    assert_eq!(r1.status, "ok");
    assert_eq!(r1.body, b"/api/users");

    let r2 = client.get("/api/v2/items").unwrap();
    assert_eq!(r2.body, b"/api/v2/items");

    let r3 = client.get("/other").unwrap();
    assert_eq!(r3.body, b"other");

    client.close();
    srv.shutdown();
}

// ─── 10. Multiple servers / clients ──────────────────────────────────────────

#[test]
fn test_multiple_servers_and_clients() {
    init();
    let make_router = |tag: &'static [u8]| {
        let mut r = Router::new();
        r.handle_func("/tag", move |w: &mut ResponseWriter, _r: &Request| {
            let _ = w.respond("ok", tag);
        });
        r
    };

    let (srv1, base1) = start(make_router(b"server1"));
    let (srv2, base2) = start(make_router(b"server2"));

    let c1 = connect(&format!("{base1}/tag"));
    let c2 = connect(&format!("{base2}/tag"));

    let r1 = c1.get("/tag").unwrap();
    assert_eq!(r1.body, b"server1");

    let r2 = c2.get("/tag").unwrap();
    assert_eq!(r2.body, b"server2");

    // Cross-connection: c1 connects to srv1, so /tag returns "server1"
    assert_ne!(r1.body, r2.body);

    c1.close();
    c2.close();
    srv1.shutdown();
    srv2.shutdown();
}

// ─── 11. Server-to-client notifications ──────────────────────────────────────

#[test]
fn test_server_notify_client() {
    init();
    let (notify_tx, notify_rx) = std::sync::mpsc::sync_channel::<Notification>(4);

    let mut router = Router::new();
    router.handle_func("/notify-test", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"");
    });
    let (srv, base) = start(router);

    let client = ClientBuilder::new()
        .on_notify(move |n| { let _ = notify_tx.send(n); })
        .connect(Keypair::generate().unwrap(), &format!("{base}/notify-test"))
        .unwrap();

    // Server knows the peer node_id after connect() returns
    let peers = srv.connected_peers();
    assert_eq!(peers.len(), 1);
    let peer_nid = peers[0];

    srv.notify(peer_nid, "test_event", "/event/path", b"event body".to_vec()).unwrap();

    let n = notify_rx.recv_timeout(Duration::from_secs(5)).expect("notification not received");
    assert_eq!(n.event, "test_event");
    assert_eq!(n.path, "/event/path");
    assert_eq!(n.body, b"event body");

    client.close();
    srv.shutdown();
}

#[test]
fn test_server_notify_with_options() {
    init();
    let (notify_tx, notify_rx) = std::sync::mpsc::sync_channel::<Notification>(4);

    let (srv, base) = start(Router::new());
    let client = ClientBuilder::new()
        .on_notify(move |n| { let _ = notify_tx.send(n); })
        .connect(Keypair::generate().unwrap(), &format!("{base}/"))
        .unwrap();

    let peers = srv.connected_peers();
    let peer_nid = peers[0];

    let opts = NotifyOptions {
        headers: vec![Header::new("x-custom", "value")],
        notify_id: Some([7u8; 16]),
    };
    srv.notify_with_options(peer_nid, "typed_event", "/typed", b"body".to_vec(), opts).unwrap();

    let n = notify_rx.recv_timeout(Duration::from_secs(5)).expect("notification not received");
    assert_eq!(n.event, "typed_event");
    assert_eq!(n.path, "/typed");
    assert_eq!(n.body, b"body");

    client.close();
    srv.shutdown();
}

#[test]
fn test_notify_all() {
    init();
    let (tx1, rx1) = std::sync::mpsc::sync_channel::<Notification>(4);
    let (tx2, rx2) = std::sync::mpsc::sync_channel::<Notification>(4);

    let (srv, base) = start(Router::new());

    let c1 = ClientBuilder::new()
        .on_notify(move |n| { let _ = tx1.send(n); })
        .connect(Keypair::generate().unwrap(), &format!("{base}/"))
        .unwrap();
    let c2 = ClientBuilder::new()
        .on_notify(move |n| { let _ = tx2.send(n); })
        .connect(Keypair::generate().unwrap(), &format!("{base}/"))
        .unwrap();

    assert_eq!(srv.connection_count(), 2);

    srv.notify_all("broadcast", "/all", b"everyone".to_vec()).unwrap();

    let n1 = rx1.recv_timeout(Duration::from_secs(5)).expect("c1 notification not received");
    let n2 = rx2.recv_timeout(Duration::from_secs(5)).expect("c2 notification not received");
    assert_eq!(n1.event, "broadcast");
    assert_eq!(n2.event, "broadcast");
    assert_eq!(n1.body, b"everyone");
    assert_eq!(n2.body, b"everyone");

    c1.close();
    c2.close();
    srv.shutdown();
}

// ─── 12. close_connection ────────────────────────────────────────────────────

#[test]
fn test_server_close_connection() {
    init();
    let mut router = Router::new();
    router.handle_func("/close-test", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"");
    });
    let (srv, base) = start(router);
    // Use a short idle timeout so the client detects the server closing its connection.
    // nwep_conn_close does not proactively send CONNECTION_CLOSE; the client relies on
    // its idle timer firing after the server stops responding.
    let client = ClientBuilder::new()
        .settings(nwep::client::Settings { timeout_ms: 200, ..nwep::client::Settings::default() })
        .connect(Keypair::generate().unwrap(), &format!("{base}/close-test"))
        .expect("connect");

    // Verify we can talk first
    let r = client.get("/close-test").unwrap();
    assert_eq!(r.status, "ok");

    // Server closes the connection; client will detect this via idle timeout (~200 ms)
    let peers = srv.connected_peers();
    assert_eq!(peers.len(), 1);
    srv.close_connection(peers[0], 0).unwrap();

    // After the connection is closed, further requests should fail
    std::thread::sleep(Duration::from_millis(400));
    let result = client.get("/close-test");
    assert!(result.is_err(), "request after close should fail");

    srv.shutdown();
}

// ─── 13. Server role in settings ─────────────────────────────────────────────

#[test]
fn test_server_with_log_server_role() {
    init();
    let mut router = Router::new();
    router.handle_func("/ping", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"pong");
    });
    let kp = Keypair::generate().unwrap();
    let settings = ServerSettings {
        role: ServerRole::LogServer,
        ..ServerSettings::default()
    };
    let (srv, el) = ServerBuilder::new("[::]:0", kp)
        .settings(settings)
        .build(router)
        .unwrap();
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });

    // Client should be able to connect to a log_server-role server and make requests
    let client = connect(&format!("{base}/ping"));
    let resp = client.get("/ping").unwrap();
    assert_eq!(resp.status, "ok");
    assert_eq!(resp.body, b"pong");

    client.close();
    srv.shutdown();
}

#[test]
fn test_server_with_anchor_role() {
    init();
    let mut router = Router::new();
    router.handle_func("/ping", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"pong");
    });
    let kp = Keypair::generate().unwrap();
    let settings = ServerSettings {
        role: ServerRole::Anchor,
        ..ServerSettings::default()
    };
    let (srv, el) = ServerBuilder::new("[::]:0", kp)
        .settings(settings)
        .build(router)
        .unwrap();
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });

    let client = connect(&format!("{base}/ping"));
    let resp = client.get("/ping").unwrap();
    assert_eq!(resp.status, "ok");

    client.close();
    srv.shutdown();
}

// ─── 14. LogServer intercept ─────────────────────────────────────────────────

#[test]
fn test_log_server_intercept_read() {
    init();
    // Build a log with one entry
    let mut log = MerkleLog::new(MemLog(vec![])).unwrap();
    let kp = Keypair::generate().unwrap();
    let entry = MerkleEntry {
        entry_type: MerkleEntryType::KeyBinding,
        timestamp: now_ns(),
        node_id: kp.node_id().unwrap(),
        pubkey: kp.public_key(),
        prev_pubkey: [0u8; 32],
        recovery_pubkey: [0u8; 32],
        signature: [0u8; 64],
    };
    log.append(&entry).unwrap();

    let ls = LogServer::new(&mut log, LogServerSettings::default()).unwrap();
    let ls_arc = Arc::new(Mutex::new(ls));

    let mut router = Router::new();
    router.handle_func("/hello", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"hello");
    });

    let server_kp = Keypair::generate().unwrap();
    let (srv, el) = ServerBuilder::new("[::]:0", server_kp)
        .log_server(ls_arc.clone())
        .build(router)
        .unwrap();
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });

    let client = connect(&format!("{base}/log/size"));

    // GET /log/size → JSON {"size":1}
    let r_size = client.get("/log/size").unwrap();
    assert_eq!(r_size.status, "ok");
    let size: u64 = parse_u64_response(&r_size.body).expect("size should be a number");
    assert_eq!(size, 1);

    // GET /log/entry/0 → binary-encoded MerkleEntry
    let r_entry = client.get("/log/entry/0").unwrap();
    assert_eq!(r_entry.status, "ok");
    assert!(!r_entry.body.is_empty());
    let decoded = MerkleEntry::decode(&r_entry.body).expect("entry should decode");
    assert_eq!(decoded.node_id, entry.node_id);

    // GET /log/proof/0 → binary-encoded MerkleProof
    let r_proof = client.get("/log/proof/0").unwrap();
    assert_eq!(r_proof.status, "ok");
    assert!(!r_proof.body.is_empty());
    // Decode and verify the proof
    let proof = nwep::merkle::MerkleProof::decode(&r_proof.body).expect("proof should decode");
    assert_eq!(proof.index, 0);

    // /hello still works (not intercepted)
    let r_hello = client.get("/hello").unwrap();
    assert_eq!(r_hello.status, "ok");
    assert_eq!(r_hello.body, b"hello");

    // /logother should not be intercepted → not_found
    let r_other = client.get("/logother").unwrap();
    assert!(!nwep::protocol::status_is_success(&r_other.status));

    client.close();
    srv.shutdown();
}

#[test]
fn test_log_server_write_entry() {
    init();
    let mut log = MerkleLog::new(MemLog(vec![])).unwrap();
    let authorized_calls: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
    let ac = authorized_calls.clone();

    let ls = LogServer::new(&mut log, LogServerSettings {
        authorize: Some(Box::new(move |_node_id, _entry| {
            *ac.lock().unwrap() += 1;
            Ok(())
        })),
    }).unwrap();
    let ls_arc = Arc::new(Mutex::new(ls));

    let server_kp = Keypair::generate().unwrap();
    let (srv, el) = ServerBuilder::new("[::]:0", server_kp)
        .log_server(ls_arc.clone())
        .build(Router::new())
        .unwrap();
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });

    let client = connect(&format!("{base}/log/size"));

    // Initially size = 0
    let r = client.get("/log/size").unwrap();
    let s: u64 = parse_u64_response(&r.body).expect("size should be a number");
    assert_eq!(s, 0);

    // POST /log/entry with an encoded MerkleEntry
    let kp = Keypair::generate().unwrap();
    let entry = MerkleEntry {
        entry_type: MerkleEntryType::KeyBinding,
        timestamp: now_ns(),
        node_id: kp.node_id().unwrap(),
        pubkey: kp.public_key(),
        prev_pubkey: [0u8; 32],
        recovery_pubkey: [0u8; 32],
        signature: [0u8; 64],
    };
    let encoded = entry.encode().unwrap();
    let r_post = client.post("/log/entry", &encoded).unwrap();
    assert_eq!(r_post.status, "created", "POST /log/entry failed: {:?}", String::from_utf8_lossy(&r_post.body));

    // Now size = 1 and authorize was called
    let r2 = client.get("/log/size").unwrap();
    let s2: u64 = parse_u64_response(&r2.body).expect("size should be a number");
    assert_eq!(s2, 1);
    assert_eq!(*authorized_calls.lock().unwrap(), 1);

    // Read back the entry
    let r_entry = client.get("/log/entry/0").unwrap();
    assert_eq!(r_entry.status, "ok");
    let got = MerkleEntry::decode(&r_entry.body).unwrap();
    assert_eq!(got.node_id, entry.node_id);

    client.close();
    srv.shutdown();
}

// ─── 15. AnchorServer intercept ──────────────────────────────────────────────

#[test]
fn test_anchor_server_intercept() {
    init();
    use nwep::bls::*;

    let bls_kp = BlsKeypair::generate().unwrap();
    let mut anchors = AnchorSet::new(1).unwrap();
    anchors.add(&bls_kp.pubkey(), true).unwrap();

    let mut log = MerkleLog::new(MemLog(vec![])).unwrap();
    let mut as_ = AnchorServer::new(&bls_kp, &mut anchors, AnchorServerSettings::default()).unwrap();

    // Create a checkpoint for epoch 1
    let mut cp = as_.create_proposal(&mut log, 1, now_ns()).unwrap();
    as_.sign_proposal(&mut cp).unwrap();
    as_.add_checkpoint(&cp).unwrap();

    let as_arc = Arc::new(Mutex::new(as_));

    let mut router = Router::new();
    router.handle_func("/status", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"up");
    });

    let server_kp = Keypair::generate().unwrap();
    let (srv, el) = ServerBuilder::new("[::]:0", server_kp)
        .anchor_server(as_arc.clone())
        .build(router)
        .unwrap();
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });

    let client = connect(&format!("{base}/checkpoint/latest"));

    // GET /checkpoint/latest → encoded Checkpoint
    let r = client.get("/checkpoint/latest").unwrap();
    assert_eq!(r.status, "ok");
    assert!(!r.body.is_empty());
    let decoded = Checkpoint::decode(&r.body).expect("checkpoint should decode");
    assert_eq!(decoded.epoch, 1);

    // GET /checkpoint/1 → same checkpoint
    let r2 = client.get("/checkpoint/1").unwrap();
    assert_eq!(r2.status, "ok");
    let decoded2 = Checkpoint::decode(&r2.body).unwrap();
    assert_eq!(decoded2.epoch, 1);

    // /status still works
    let r3 = client.get("/status").unwrap();
    assert_eq!(r3.status, "ok");
    assert_eq!(r3.body, b"up");

    // /checkpointother should not be intercepted → not_found
    let r4 = client.get("/checkpointother").unwrap();
    assert!(!nwep::protocol::status_is_success(&r4.status));

    client.close();
    srv.shutdown();
}

// ─── 16. Both LogServer + AnchorServer on the same server ────────────────────

#[test]
fn test_log_and_anchor_server_together() {
    init();
    use nwep::bls::*;

    // Build log
    let mut log = MerkleLog::new(MemLog(vec![])).unwrap();
    let kp = Keypair::generate().unwrap();
    let entry = MerkleEntry {
        entry_type: MerkleEntryType::KeyBinding,
        timestamp: now_ns(),
        node_id: kp.node_id().unwrap(),
        pubkey: kp.public_key(),
        prev_pubkey: [0u8; 32],
        recovery_pubkey: [0u8; 32],
        signature: [0u8; 64],
    };
    log.append(&entry).unwrap();

    let ls = LogServer::new(&mut log, LogServerSettings::default()).unwrap();
    let ls_arc = Arc::new(Mutex::new(ls));

    // Build anchor server
    let bls_kp = BlsKeypair::generate().unwrap();
    let mut anchors = AnchorSet::new(1).unwrap();
    anchors.add(&bls_kp.pubkey(), true).unwrap();
    let mut log2 = MerkleLog::new(MemLog(vec![])).unwrap();
    let mut as_ = AnchorServer::new(&bls_kp, &mut anchors, AnchorServerSettings::default()).unwrap();
    let mut cp = as_.create_proposal(&mut log2, 1, now_ns()).unwrap();
    as_.sign_proposal(&mut cp).unwrap();
    as_.add_checkpoint(&cp).unwrap();
    let as_arc = Arc::new(Mutex::new(as_));

    // Route for non-intercepted paths
    let mut router = Router::new();
    router.handle_func("/ping", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"pong");
    });

    let server_kp = Keypair::generate().unwrap();
    let (srv, el) = ServerBuilder::new("[::]:0", server_kp)
        .log_server(ls_arc)
        .anchor_server(as_arc)
        .build(router)
        .unwrap();
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });

    let client = connect(&format!("{base}/log/size"));

    // Log sub-server
    let r_size = client.get("/log/size").unwrap();
    assert_eq!(r_size.status, "ok");
    let size: u64 = parse_u64_response(&r_size.body).expect("size should be a number");
    assert_eq!(size, 1);

    let r_entry = client.get("/log/entry/0").unwrap();
    assert_eq!(r_entry.status, "ok");

    // Anchor sub-server
    let r_cp = client.get("/checkpoint/latest").unwrap();
    assert_eq!(r_cp.status, "ok");
    let cp_dec = Checkpoint::decode(&r_cp.body).unwrap();
    assert_eq!(cp_dec.epoch, 1);

    // Custom route still works
    let r_ping = client.get("/ping").unwrap();
    assert_eq!(r_ping.status, "ok");
    assert_eq!(r_ping.body, b"pong");

    // Unknown paths → not_found
    let r_unknown = client.get("/unknown-path-xyz").unwrap();
    assert!(!nwep::protocol::status_is_success(&r_unknown.status));

    client.close();
    srv.shutdown();
}

// ─── 17. conn.role in on_request ─────────────────────────────────────────────

#[test]
fn test_conn_role_in_request() {
    init();
    let roles_seen: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(vec![]));
    let rs = roles_seen.clone();

    let mut router = Router::new();
    router.handle_func("/role", move |w: &mut ResponseWriter, r: &Request| {
        rs.lock().unwrap().push(r.conn.role.as_str().to_string());
        let _ = w.respond("ok", b"");
    });

    let (srv, base) = start(router);
    let client = connect(&format!("{base}/role"));
    client.get("/role").unwrap();

    // Clients always have role "regular"
    let seen = roles_seen.lock().unwrap();
    assert!(!seen.is_empty());
    assert_eq!(seen[0], "regular");

    client.close();
    srv.shutdown();
}

// ─── 18. Request method is set correctly ─────────────────────────────────────

#[test]
fn test_request_method_routing() {
    init();
    let mut router = Router::new();
    router.handle_func("/method", |w: &mut ResponseWriter, r: &Request| {
        let _ = w.respond("ok", r.method.as_bytes());
    });

    let (srv, base) = start(router);
    let client = connect(&format!("{base}/method"));

    let r_read = client.get("/method").unwrap();
    assert_eq!(r_read.body, nwep::protocol::METHOD_READ.as_bytes());

    let r_write = client.post("/method", b"").unwrap();
    assert_eq!(r_write.body, nwep::protocol::METHOD_WRITE.as_bytes());

    let r_custom = client.fetch(nwep::protocol::METHOD_UPDATE, "/method", b"").unwrap();
    assert_eq!(r_custom.body, nwep::protocol::METHOD_UPDATE.as_bytes());

    client.close();
    srv.shutdown();
}

// ─── 19. Request / trace IDs are populated ───────────────────────────────────

#[test]
fn test_request_trace_ids_present() {
    init();
    let mut router = Router::new();
    router.handle_func("/ids", |w: &mut ResponseWriter, r: &Request| {
        // Echo request_id and trace_id as response body
        let mut body = r.request_id.to_vec();
        body.extend_from_slice(&r.trace_id);
        let _ = w.respond("ok", &body);
    });

    let (srv, base) = start(router);
    let client = connect(&format!("{base}/ids"));
    let resp = client.get("/ids").unwrap();
    assert_eq!(resp.body.len(), 32);
    // At least one of them should be non-zero
    assert!(resp.body.iter().any(|&b| b != 0));

    client.close();
    srv.shutdown();
}

// ─── 20. Client on_notify not called when no notification sent ───────────────

#[test]
fn test_no_spurious_notifications() {
    init();
    let count: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
    let cc = count.clone();

    let (srv, base) = start(Router::new());
    let client = ClientBuilder::new()
        .on_notify(move |_| { *cc.lock().unwrap() += 1; })
        .connect(Keypair::generate().unwrap(), &format!("{base}/"))
        .unwrap();

    // Do some requests without any notifications being sent
    // (Router has no routes so they'll all 404, but no notifications)
    let _ = client.get("/anything");
    let _ = client.get("/anything");
    std::thread::sleep(Duration::from_millis(50));

    assert_eq!(*count.lock().unwrap(), 0, "no spurious notifications expected");

    client.close();
    srv.shutdown();
}

// ─── 21. Server shutdown while client connected ──────────────────────────────

#[test]
fn test_server_shutdown_disconnects_client() {
    init();
    let mut router = Router::new();
    router.handle_func("/pre-shutdown", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"");
    });
    let (srv, base) = start(router);
    // Use a short client idle timeout so it detects the server going away promptly.
    // nwep_server_close does not proactively send CONNECTION_CLOSE; the client must
    // rely on its idle timeout firing after the server stops responding.
    let client = ClientBuilder::new()
        .settings(nwep::client::Settings { timeout_ms: 200, ..nwep::client::Settings::default() })
        .connect(Keypair::generate().unwrap(), &format!("{base}/pre-shutdown"))
        .expect("connect");

    let r = client.get("/pre-shutdown").unwrap();
    assert_eq!(r.status, "ok");

    // Shutdown server; the client's idle timeout (~200 ms) will fire before the
    // test's 300 ms sleep ends, setting state.disconnected = true.
    srv.shutdown();
    std::thread::sleep(Duration::from_millis(300));
    let r2 = client.get("/pre-shutdown");
    assert!(r2.is_err(), "request to shut-down server should fail");
}

// ─── 22. Custom server settings (max_streams, timeout) ───────────────────────

#[test]
fn test_custom_server_settings() {
    init();
    let mut router = Router::new();
    router.handle_func("/settings", |w: &mut ResponseWriter, _r: &Request| {
        let _ = w.respond("ok", b"custom settings");
    });
    let kp = Keypair::generate().unwrap();
    let settings = ServerSettings {
        max_streams: 50,
        timeout_ms: 10_000,
        ..ServerSettings::default()
    };
    let (srv, el) = ServerBuilder::new("[::]:0", kp)
        .settings(settings)
        .build(router)
        .unwrap();
    let base = srv.url("/").trim_end_matches('/').to_string();
    std::thread::spawn(move || { let _ = el.run(); });

    let client = connect(&format!("{base}/settings"));
    let resp = client.get("/settings").unwrap();
    assert_eq!(resp.status, "ok");
    assert_eq!(resp.body, b"custom settings");

    client.close();
    srv.shutdown();
}

// ─── 23. on_request conn info: node_id matches client ────────────────────────

#[test]
fn test_conn_node_id_in_request() {
    init();
    let seen_nid: Arc<Mutex<Option<NodeId>>> = Arc::new(Mutex::new(None));
    let sn = seen_nid.clone();

    let mut router = Router::new();
    router.handle_func("/nid", move |w: &mut ResponseWriter, r: &Request| {
        *sn.lock().unwrap() = Some(r.conn.node_id);
        let _ = w.respond("ok", b"");
    });

    let (srv, base) = start(router);

    let client_kp = Keypair::generate().unwrap();
    let client_nid = client_kp.node_id().unwrap();
    let client = ClientBuilder::new()
        .connect(client_kp, &format!("{base}/nid"))
        .unwrap();

    client.get("/nid").unwrap();

    let got = seen_nid.lock().unwrap().expect("node_id should be set in handler");
    assert_eq!(got, client_nid);

    client.close();
    srv.shutdown();
}
