#!/usr/bin/env python3
"""
CLI peer (offline) — all 4 public bootstrap nodes — 2-minute stability watch

offline=True:  no DHT, no random-walk → clean logs
bootstrap=False in create_and_start_peer → we call peer.bootstrap() manually

Run:  uv run python -u test_bootstrap_stability.py
"""

import sys
import time

import trio

from libp2p.peer.id import ID
from py_ipfs_lite.cli import create_and_start_peer, DEFAULT_BOOTSTRAP_PEERS
from py_ipfs_lite.config import Config

# Force line-buffered stdout so output shows even when piped
sys.stdout.reconfigure(line_buffering=True)

OBSERVE_SECS = 130
CHECK_INTERVAL = 10

T0 = time.monotonic()

def ts() -> str:
    e = time.monotonic() - T0
    m, s = divmod(e, 60)
    h, m = divmod(m, 60)
    return f"+{int(h):02d}:{int(m):02d}:{int(s):02d}"

def p(*args, **kwargs):
    print(*args, **kwargs, flush=True)

LABELS = {
    addr.split("/p2p/")[1]: addr.split("/p2p/")[1][-8:]
    for addr in DEFAULT_BOOTSTRAP_PEERS
}


async def main() -> None:
    p()
    p("=" * 65)
    p("  CLI Peer (online) — 4 bootstrap nodes — 2-min stability")
    p("=" * 65)
    p("\n  Bootstrap peers:")
    for addr in DEFAULT_BOOTSTRAP_PEERS:
        pid = addr.split("/p2p/")[1]
        p(f"    [{LABELS[pid]}]  {addr}")
    p()

    cfg = Config(offline=False, blockstore_type="memory")

    async with create_and_start_peer(port=0, seed=None, config=cfg, bootstrap=False) as peer:
        raw_host = peer.host._host
        p(f"  [{ts()}]  Peer ready  id={raw_host.get_id()}")

        # ── Bootstrap to all 4 nodes ──────────────────────────────────
        p(f"  [{ts()}]  Bootstrapping to all 4 nodes …")
        try:
            with trio.fail_after(30):
                await peer.bootstrap(DEFAULT_BOOTSTRAP_PEERS)
            p(f"  [{ts()}]  Bootstrap done")
        except Exception as exc:
            p(f"  [{ts()}]  Bootstrap error (may be partial): {exc}")

        # ── Initial snapshot ──────────────────────────────────────────
        network = raw_host.get_network()
        peer_ids: dict[str, ID] = {}
        for addr in DEFAULT_BOOTSTRAP_PEERS:
            pid_str = addr.split("/p2p/")[1]
            try:
                peer_ids[pid_str] = ID.from_base58(pid_str)
            except Exception:
                pass

        p(f"\n  [{ts()}]  Initial connection status:")
        for pid_str, pid in peer_ids.items():
            conns = network.connections.get(pid, [])
            alive = any(not c.is_closed for c in conns)
            p(f"    [{LABELS[pid_str]}]  {'connected' if alive else 'NOT connected'}")

        p()
        p(f"  Monitoring for {OBSERVE_SECS} s (check every {CHECK_INTERVAL} s) …")
        header = "  TIME        " + "  ".join(f"[{LABELS[pid]}]" for pid in peer_ids)
        p(header)
        p("  " + "-" * (len(header) - 2))

        history: dict[str, list[bool]] = {pid: [] for pid in peer_ids}
        deadline = trio.current_time() + OBSERVE_SECS

        while trio.current_time() < deadline:
            await trio.sleep(CHECK_INTERVAL)
            network = raw_host.get_network()
            statuses = []
            for pid_str, pid in peer_ids.items():
                conns = network.connections.get(pid, [])
                alive = any(not c.is_closed for c in conns)
                history[pid_str].append(alive)
                statuses.append(" Y " if alive else " N ")
            remaining = int(deadline - trio.current_time())
            p(f"  {ts():10s}   {'    '.join(statuses)}    ({remaining}s left)")

        # ── Summary ───────────────────────────────────────────────────
        p()
        p("=" * 65)
        p("  RESULTS  (Y = connected throughout, N = dropped)")
        p("=" * 65)
        all_pass = True
        for addr in DEFAULT_BOOTSTRAP_PEERS:
            pid_str = addr.split("/p2p/")[1]
            snaps = history[pid_str]
            drops = snaps.count(False)
            final = snaps[-1] if snaps else False
            mark = "PASS" if final else "FAIL"
            detail = "stable" if drops == 0 else f"{drops} drop(s)"
            if not final:
                all_pass = False
            p(f"  [{mark}]  [{LABELS[pid_str]}]  {detail}  ({len(snaps)} checks)")

        total = sum(len(v) for v in history.values())
        drops_total = sum(v.count(False) for v in history.values())
        p()
        p(f"  Checks: {total}  |  Drops: {drops_total}  |  Duration: {OBSERVE_SECS} s")
        p()
        if all_pass and drops_total == 0:
            p("  ALL 4 nodes stayed connected for the full 2+ minutes.")
        elif all_pass:
            p("  All 4 connected at end — some transient drops during the run.")
        else:
            p("  One or more nodes were not connected at the end.")
        p("=" * 65)


if __name__ == "__main__":
    trio.run(main)
