//! Integration test for the iroh mDNS address-lookup rendezvous path —
//! the `iroh-mdns-address-lookup` crate (bumped 0.2 → 0.3 in peat-mesh#210).
//!
//! Two `IrohTransport`s built via `with_discovery` register a
//! `MdnsAddressLookup` in their endpoints' `address_lookup` chain. The dialer
//! connects to the acceptor **by `EndpointId` only** (`connect_by_id`, which
//! hands iroh an `EndpointAddr` with an empty `addrs` set). The connection can
//! therefore succeed *only* if the acceptor advertised its address over
//! `_peat._udp.local` and the dialer's lookup chain resolved it via mDNS.
//!
//! This pins the rc.1 / iroh-mdns-address-lookup 0.3 end-to-end rendezvous
//! behaviour that `cargo build` cannot catch — advertise + browse + resolve,
//! then the QUIC handshake — i.e. the runtime mDNS behaviour flagged in the
//! #210 QA review as the residual pre-rc.31 gate. (Both endpoints run the same
//! crate version, so this exercises the consumer-visible rendezvous round-trip
//! rather than cross-version service-name compatibility specifically.)
//!
//! `#[ignore]`: most containerized CI blocks multicast joins. Run on a
//! multicast-capable runner (the QA Review runner, which peat-node already
//! uses for local mDNS) or bare metal:
//!
//! ```sh
//! cargo test --features automerge-backend --test mdns_iroh_address_lookup -- --ignored
//! ```

#![cfg(feature = "automerge-backend")]

use std::sync::Arc;
use std::time::{Duration, Instant};

use peat_mesh::network::IrohTransport;

#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
#[ignore = "requires multicast in CI; run with --ignored on bare metal"]
async fn dialer_resolves_acceptor_by_id_via_mdns() {
    // Both endpoints register MdnsAddressLookup in their address_lookup chain.
    // with_discovery generates a fresh random EndpointId per instance, so there
    // is no identity collision across parallel test binaries.
    let acceptor = Arc::new(
        IrohTransport::with_discovery("mdns-accept")
            .await
            .expect("acceptor with_discovery"),
    );
    let dialer = IrohTransport::with_discovery("mdns-dial")
        .await
        .expect("dialer with_discovery");

    let acceptor_id = acceptor.endpoint_id();
    let dialer_id = dialer.endpoint_id();

    // Drive the acceptor's accept loop so the inbound QUIC handshake completes.
    // `accept()` yields Ok(None) on a transient handshake error — keep going
    // until a real connection arrives (or the endpoint closes).
    let acc_task = {
        let acceptor = Arc::clone(&acceptor);
        tokio::spawn(async move {
            loop {
                match acceptor.accept().await {
                    Ok(Some(conn)) => break Some(conn.remote_id()),
                    Ok(None) => continue,
                    Err(_) => break None,
                }
            }
        })
    };

    // Dial by id only. With an empty EndpointAddr.addrs, the only way this
    // resolves is the mDNS address-lookup chain. Retry until the acceptor's
    // mDNS record has propagated (advertise + browse + QUIC handshake).
    //
    // Keep the successful connection bound (not dropped at the end of the
    // match arm): if the dialer tears it down before the acceptor's
    // `incoming.await` finishes the handshake, the acceptor sees a
    // mid-handshake close and `accept()` returns Ok(None) — a small flake
    // window on slower runs. Holding `_dial_conn` alive until after the
    // acceptor join closes that window (peat-mesh#212 QA).
    let deadline = Instant::now() + Duration::from_secs(20);
    let mut dial_conn = None;
    while Instant::now() < deadline {
        if let Ok(Some(conn)) = dialer.connect_by_id(acceptor_id).await {
            dial_conn = Some(conn);
            break;
        }
        tokio::time::sleep(Duration::from_millis(300)).await;
    }
    assert!(
        dial_conn.is_some(),
        "dialer failed to resolve + connect to the acceptor by id via mDNS \
         within 20s — the iroh-mdns-address-lookup rendezvous (`_peat._udp.local`) \
         did not complete. If this regressed after the 0.2→0.3 bump, the \
         advertise/browse/resolve rendezvous behaviour changed."
    );

    // Confirm the acceptor side observed the dialer — handshake completed
    // end-to-end, not just a half-open dial.
    let observed = tokio::time::timeout(Duration::from_secs(5), acc_task)
        .await
        .expect("acceptor accept loop did not yield within 5s of a successful dial")
        .expect("acceptor accept task panicked");

    // Connection held open across the acceptor join above; safe to drop now.
    drop(dial_conn);
    assert_eq!(
        observed,
        Some(dialer_id),
        "acceptor did not observe the dialer's endpoint id on the accepted connection"
    );
}
