//! Narrow trait abstracting the QUIC-stream surface peat-protocol's
//! formation-handshake needs from a mesh connection (peat-mesh#167).
//!
//! Replaces the prior `pub use iroh::endpoint::Connection` re-export.
//! peat-protocol's [`formation_handshake`] previously took
//! `&iroh::endpoint::Connection` directly, exposing the full
//! `Connection` API surface — every method (datagrams, paths_stream,
//! close, remote_node_id, …) was reachable, and any iroh upstream
//! addition would silently widen what peat-protocol could reach for.
//! The transport-agnosticism gate was enforced only at the import
//! path, not the API shape.
//!
//! [`QuicMeshConnection`] names exactly the four methods the
//! handshake legitimately uses:
//!
//! - [`open_bi`](QuicMeshConnection::open_bi) — open a bidirectional
//!   stream pair for the initiator side of the formation challenge.
//! - [`accept_bi`](QuicMeshConnection::accept_bi) — accept a
//!   bidirectional stream pair on the responder side.
//! - [`close_reason`](QuicMeshConnection::close_reason) — probe the
//!   close state of the connection (consumed by `IrohMeshConnection`'s
//!   `MeshConnection::is_alive` / `disconnect_reason` chain).
//! - [`remote_endpoint_id`](QuicMeshConnection::remote_endpoint_id) —
//!   identify the remote peer for log/audit attribution.
//!
//! A future iroh `Connection` method addition does not widen this
//! trait by default — peat-protocol stays narrowed to the four methods
//! above unless the trait itself is amended.
//!
//! [`formation_handshake`]: https://github.com/defenseunicorns/peat/blob/main/peat-protocol/src/network/formation_handshake.rs
//!
//! ## Naming
//!
//! Disambiguates from the pre-existing
//! [`crate::transport::MeshConnection`] trait, which operates on
//! `NodeId`-tagged connections and exposes lifecycle predicates
//! (`peer_id`, `is_alive`, `connected_at`, `disconnect_reason`) rather
//! than the QUIC-stream surface this trait abstracts. The `Quic`
//! prefix marks the stream-oriented half.

use async_trait::async_trait;
use iroh::endpoint::{Connection, ConnectionError, RecvStream, SendStream};
use iroh::EndpointId;

/// Narrow QUIC-stream + identity surface that peat-protocol's
/// formation-handshake consumes. See the module-level docs for the
/// motivation; see the per-method docs for the iroh `Connection`
/// method each delegates to.
#[async_trait]
pub trait QuicMeshConnection: Send + Sync {
    /// Open a bidirectional stream pair. Delegates to
    /// [`iroh::endpoint::Connection::open_bi`]'s `.await` result.
    async fn open_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError>;

    /// Accept a bidirectional stream pair. Delegates to
    /// [`iroh::endpoint::Connection::accept_bi`]'s `.await` result.
    async fn accept_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError>;

    /// Return the close reason if the connection has been closed, else
    /// `None`. Delegates to
    /// [`iroh::endpoint::Connection::close_reason`].
    fn close_reason(&self) -> Option<ConnectionError>;

    /// Return the remote peer's [`EndpointId`]. Delegates to
    /// `iroh::endpoint::Connection::remote_id` on the default
    /// (non-0-RTT) `Connection` shape; that overload is infallible
    /// because the handshake has already completed by the time the
    /// `Connection` handle is observable.
    fn remote_endpoint_id(&self) -> EndpointId;
}

#[async_trait]
impl QuicMeshConnection for Connection {
    async fn open_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError> {
        self.open_bi().await
    }

    async fn accept_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError> {
        self.accept_bi().await
    }

    fn close_reason(&self) -> Option<ConnectionError> {
        self.close_reason()
    }

    fn remote_endpoint_id(&self) -> EndpointId {
        self.remote_id()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Compile-only typecheck — confirms `iroh::endpoint::Connection`
    /// satisfies the trait surface. If a future iroh release removes
    /// or renames any of the four methods, this fails to compile
    /// at peat-mesh CI rather than at a downstream peat-protocol
    /// build site. Same shape as the existing `reexport_pin` module
    /// for type re-exports.
    #[allow(dead_code)]
    fn _connection_impls_quic_mesh_connection(c: Connection) {
        let _: &dyn QuicMeshConnection = &c;
    }

    /// Runtime dispatch-resolution proof for the four trait methods —
    /// uses a `MockConn` impl whose methods return synthetic / sentinel
    /// values, casts it as `&dyn QuicMeshConnection`, and confirms each
    /// method dispatches through the trait object to the impl. Catches
    /// `async_trait` codegen regressions (object-safety lapses,
    /// lifetime drift on the returned `Pin<Box<dyn Future + Send>>`,
    /// async fn-in-trait dispatch resolution) at runtime without
    /// depending on iroh's accept-loop in-process timing — that
    /// scenario is fragile under shared CI runners and the surface
    /// coverage it would add lands cleanly via peat-protocol's
    /// `formation_handshake` e2e tests once the consumer-side
    /// migration PR ships (peat-side rc.16, tracked in [peat#932]).
    ///
    /// [peat#932]: https://github.com/defenseunicorns/peat/issues/932
    #[tokio::test]
    async fn dyn_dispatch_resolves_via_async_trait() {
        use async_trait::async_trait;

        // Sentinel `EndpointId` derived from a deterministic seed so the
        // assertion is exact, not "any EndpointId".
        let sentinel_id = crate::network::iroh_transport::IrohTransport::endpoint_id_from_seed(
            "quic-mesh-conn-mock",
        );

        struct MockConn {
            id: EndpointId,
        }

        #[async_trait]
        impl QuicMeshConnection for MockConn {
            async fn open_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError> {
                // Use a stable closed-connection error so the
                // assertion is exact, not "any error".
                Err(ConnectionError::LocallyClosed)
            }

            async fn accept_bi(&self) -> Result<(SendStream, RecvStream), ConnectionError> {
                Err(ConnectionError::LocallyClosed)
            }

            fn close_reason(&self) -> Option<ConnectionError> {
                Some(ConnectionError::TimedOut)
            }

            fn remote_endpoint_id(&self) -> EndpointId {
                self.id
            }
        }

        let mock = MockConn { id: sentinel_id };
        let dyn_ref: &dyn QuicMeshConnection = &mock;

        // Sync methods — dispatch resolution.
        assert_eq!(dyn_ref.remote_endpoint_id(), sentinel_id);
        assert!(matches!(
            dyn_ref.close_reason(),
            Some(ConnectionError::TimedOut)
        ));

        // Async methods — async_trait codegen + Pin<Box<...>>
        // dispatch resolution.
        assert!(matches!(
            dyn_ref.open_bi().await,
            Err(ConnectionError::LocallyClosed)
        ));
        assert!(matches!(
            dyn_ref.accept_bi().await,
            Err(ConnectionError::LocallyClosed)
        ));
    }
}
