#!/usr/bin/env python3
"""
Minimal direct-dial test: py-libp2p → Kubo, one-shot.
Starts Kubo, dials it directly (no bootstrap layer), and logs the exact error.
"""
import logging
import os
import subprocess
import sys
import tempfile
import time

import trio

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] [%(name)s] %(message)s",
    stream=sys.stderr,
)
# Quieten noise
logging.getLogger("multiaddr").setLevel(logging.WARNING)

from libp2p import new_host
from libp2p.crypto.ed25519 import create_new_key_pair
from libp2p.crypto.x25519 import create_new_key_pair as x25519_kp
from libp2p.peer.peerinfo import info_from_p2p_addr
from libp2p.security.noise.transport import Transport as NoiseTransport
from libp2p.security.tls.transport import TLSTransport
from multiaddr import Multiaddr


def start_kubo(ipfs_path: str):
    env = {**os.environ, "IPFS_PATH": ipfs_path}
    subprocess.run(["ipfs", "init", "--profile=test"], env=env, check=True, capture_output=True)
    subprocess.run(["ipfs", "config", "--json", "Addresses.Swarm", '["/ip4/127.0.0.1/tcp/0"]'],
                   env=env, check=True, capture_output=True)
    subprocess.run(["ipfs", "bootstrap", "rm", "--all"], env=env, check=True, capture_output=True)
    subprocess.run(["ipfs", "config", "Addresses.API", "/ip4/127.0.0.1/tcp/0"], env=env, check=True, capture_output=True)
    subprocess.run(["ipfs", "config", "Addresses.Gateway", "/ip4/127.0.0.1/tcp/0"], env=env, check=True, capture_output=True)

    log_path = os.path.join(ipfs_path, "daemon.log")
    log_file = open(log_path, "w")
    proc = subprocess.Popen(["ipfs", "daemon"], env=env, stdout=log_file, stderr=log_file)
    deadline = time.time() + 30
    while time.time() < deadline:
        time.sleep(0.5)
        if proc.poll() is not None:
            raise RuntimeError("Kubo exited early: " + open(log_path).read()[-2000:])
        try:
            if "Daemon is ready" in open(log_path).read():
                break
        except FileNotFoundError:
            pass
    else:
        proc.terminate()
        raise RuntimeError("Kubo did not start in 30s")

    peer_id = subprocess.check_output(["ipfs", "id", "-f=<id>"], env=env).decode().strip()
    addrs = subprocess.check_output(["ipfs", "id", "-f=<addrs>"], env=env).decode().strip().splitlines()
    addr = next((a.strip() for a in addrs if "127.0.0.1" in a and "/tcp/" in a), None)
    if not addr:
        proc.terminate()
        raise RuntimeError("No TCP addr found")
    return proc, peer_id, addr


async def try_connect(host, info, label: str):
    print(f"\n--- Trying {label} ---", flush=True)
    try:
        with trio.fail_after(8):
            await host.connect(info)
        print(f"✅ {label}: CONNECTED!", flush=True)
        return True
    except Exception as e:
        print(f"❌ {label}: {type(e).__name__}: {e}", flush=True)
        return False


async def main():
    with tempfile.TemporaryDirectory(prefix="kubo_direct_") as ipfs_path:
        print("Starting Kubo …")
        proc, kubo_id, kubo_addr = start_kubo(ipfs_path)
        print(f"Kubo addr: {kubo_addr}")

        try:
            host_key = create_new_key_pair()
            noise_key = x25519_kp()

            # Test 1: Noise + TLS (both offered — py-libp2p default)
            sec_opt_both = {
                "/noise": NoiseTransport(host_key, noise_privkey=noise_key.private_key),
                "/tls/1.0.0": TLSTransport(host_key),
            }
            host1 = new_host(
                key_pair=host_key,
                listen_addrs=[Multiaddr("/ip4/127.0.0.1/tcp/0")],
                sec_opt=sec_opt_both,
            )
            async with host1.run([Multiaddr("/ip4/127.0.0.1/tcp/0")]):
                info = info_from_p2p_addr(Multiaddr(kubo_addr))
                r1 = await try_connect(host1, info, "Noise+TLS (both offered)")

            host_key2 = create_new_key_pair()
            # Test 2: Noise ONLY
            noise_key2 = x25519_kp()
            sec_opt_noise = {
                "/noise": NoiseTransport(host_key2, noise_privkey=noise_key2.private_key),
            }
            host2 = new_host(
                key_pair=host_key2,
                listen_addrs=[Multiaddr("/ip4/127.0.0.1/tcp/0")],
                sec_opt=sec_opt_noise,
            )
            async with host2.run([Multiaddr("/ip4/127.0.0.1/tcp/0")]):
                info2 = info_from_p2p_addr(Multiaddr(kubo_addr))
                r2 = await try_connect(host2, info2, "Noise ONLY")

            host_key3 = create_new_key_pair()
            # Test 3: TLS ONLY
            sec_opt_tls = {
                "/tls/1.0.0": TLSTransport(host_key3),
            }
            host3 = new_host(
                key_pair=host_key3,
                listen_addrs=[Multiaddr("/ip4/127.0.0.1/tcp/0")],
                sec_opt=sec_opt_tls,
            )
            async with host3.run([Multiaddr("/ip4/127.0.0.1/tcp/0")]):
                info3 = info_from_p2p_addr(Multiaddr(kubo_addr))
                r3 = await try_connect(host3, info3, "TLS ONLY")

        finally:
            proc.terminate()
            proc.wait(timeout=5)

        print("\n=== RESULTS ===")
        print(f"Noise+TLS : {'PASS' if r1 else 'FAIL'}")
        print(f"Noise only: {'PASS' if r2 else 'FAIL'}")
        print(f"TLS only  : {'PASS' if r3 else 'FAIL'}")

trio.run(main)
