import pathlib

p = pathlib.Path("crates/spt-daemon/tests/twohost_web.rs"); s = p.read_text(encoding="utf-8")


def rep(old, new):
    global s
    assert s.count(old) == 1, old[:60]
    s = s.replace(old, new)


rep("use std::path::{Path, PathBuf};\n", "use std::collections::HashSet;\nuse std::path::Path;\n")
rep("use spt_daemon::broker::Broker;\n", "use spt_daemon::brain::Brain;\nuse spt_daemon::broker::Broker;\n")
rep("\n#[allow(dead_code)]\nfn _paths_are_used(_: PathBuf) {}\n", "")
rep("const OPEN_ENDPOINT: &str = \"w1-open\";\n",
    "/// The requester cells role B waits to have served (fetch, range, deny).\nconst A_CELLS: usize = 3;\nconst OPEN_ENDPOINT: &str = \"w1-open\";\n")
old_tail = '''    println!("TWOHOST OK: role B serving open.md + locked.md for {:?}", rig.wait);
    thread::sleep(rig.wait);
    stop.store(true, std::sync::atomic::Ordering::Relaxed);
    println!("TWOHOST OK: role B served its window");
}
'''
new_tail = '''    // Serve until every A cell has been seen — three peer-initiated streams
    // on this broker (the fetch, the range, the deny), observed through the
    // broker's own stream table — then a grace for the last serve to finish.
    // The rig deadline caps it; a B that never sees A fails loud, not late.
    let mut brain = connect_retry(&broker_name);
    let mut seen: HashSet<u64> = HashSet::new();
    let end = Instant::now() + rig.wait;
    println!("TWOHOST OK: role B serving open.md + locked.md (waiting for {A_CELLS} peer streams)");
    while seen.len() < A_CELLS {
        assert!(
            Instant::now() < end,
            "role B saw only {} of {A_CELLS} requester streams within {:?}",
            seen.len(),
            rig.wait
        );
        if let Ok(reply) = brain.net_streams() {
            for s in reply.streams {
                if !s.initiated_locally && seen.insert(s.stream_id) {
                    println!("TWOHOST-WEB role B: peer stream {} ({}/{A_CELLS})", s.stream_id, seen.len());
                }
            }
        }
        thread::sleep(Duration::from_millis(250));
    }
    thread::sleep(Duration::from_secs(10));
    stop.store(true, std::sync::atomic::Ordering::Relaxed);
    println!("TWOHOST OK: role B served all {A_CELLS} requester cells");
}

fn connect_retry(name: &str) -> Brain {
    for _ in 0..300 {
        if let Ok(b) = Brain::cold_start(name, 1) {
            return b;
        }
        thread::sleep(Duration::from_millis(10));
    }
    panic!("brain could not connect");
}
'''
rep(old_tail, new_tail)
p.write_text(s, encoding="utf-8")

p = pathlib.Path(".github/workflows/golden.yml"); s = p.read_text(encoding="utf-8")
old = "|transport_death_eof|two_origin_spanning|twohost)$/))"
assert s.count(old) == 1
s = s.replace(old, "|transport_death_eof|two_origin_spanning|twohost|twohost_web)$/))")
old_b = '''      - name: Two-host ladder — role B (seed-holder/server)
        shell: bash
        run: bash .github/bench/wrap.sh twohost-ladder-b -- cargo test -p spt-daemon --test twohost -- --nocapture
        env:
          SPT_TWO_HOST: "1"
          SPT_TWO_HOST_ROLE: "b"
          SPT_TWO_HOST_SECRET: "rig-twohost-ci"
          SPT_TWO_HOST_PEER_IP: "100.68.35.65" # hfenduleam (tailscale)
          SPT_TWO_HOST_WAIT_SECS: "900"
          SPT_TWO_HOST_NOTIFY_BIN: ${{ github.workspace }}/.adapter-notify/target/debug/notify-shell
'''
assert s.count(old_b) == 1
s = s.replace(old_b, old_b + '''      # WEBSERVE W1 (releases#249): cross-node serving cells on the real rig —
      # this host OWNS the files, hfenduleam fetches them through its own
      # loopback listener. Its own bin, its own ports (+20), after the ladder.
      # [int->REQ-WEB-CROSS-NODE-PROXY]
      - name: Two-host web serving — role B (owner)
        shell: bash
        run: bash .github/bench/wrap.sh twohost-web-b -- cargo test -p spt-daemon --test twohost_web -- --nocapture
        env:
          SPT_TWO_HOST: "1"
          SPT_TWO_HOST_ROLE: "b"
          SPT_TWO_HOST_SECRET: "rig-twohost-ci"
          SPT_TWO_HOST_PEER_IP: "100.68.35.65" # hfenduleam (tailscale)
          SPT_TWO_HOST_WAIT_SECS: "900"
''')
old_a = '''      - name: Two-host ladder — role A (joiner/driver)
        shell: pwsh
        run: .github/bench/wrap.ps1 -Step "twohost-ladder-a" -Command "cargo test -p spt-daemon --test twohost -- --nocapture"
        env:
          SPT_TWO_HOST: "1"
          SPT_TWO_HOST_ROLE: "a"
          SPT_TWO_HOST_SECRET: "rig-twohost-ci"
          SPT_TWO_HOST_PEER_IP: "100.98.197.12" # kitsubito (tailscale)
          SPT_TWO_HOST_WAIT_SECS: "900"
          SPT_TWO_HOST_NOTIFY_BIN: ${{ github.workspace }}\\.adapter-notify\\target\\debug\\notify-shell.exe
'''
assert s.count(old_a) == 1, s.count(old_a)
s = s.replace(old_a, old_a + '''      # WEBSERVE W1 (releases#249): the requester half of the cross-node
      # serving cells — three cells, each its own listener + broker, fetching
      # kitsubito's files through this host's loopback. After the ladder.
      # [int->REQ-WEB-CROSS-NODE-PROXY]
      - name: Two-host web serving — role A (requester)
        shell: pwsh
        run: .github/bench/wrap.ps1 -Step "twohost-web-a" -Command "cargo test -p spt-daemon --test twohost_web -- --nocapture"
        env:
          SPT_TWO_HOST: "1"
          SPT_TWO_HOST_ROLE: "a"
          SPT_TWO_HOST_SECRET: "rig-twohost-ci"
          SPT_TWO_HOST_PEER_IP: "100.98.197.12" # kitsubito (tailscale)
          SPT_TWO_HOST_WAIT_SECS: "900"
''')
p.write_text(s, encoding="utf-8")
print("twohost_web + golden edited")
