//! Minimal peer descriptor used by [`super::iroh_transport::IrohTransport::connect_peer`].
//!
//! This type lived in `peat-protocol::network::peer_config::PeerInfo` until
//! ADR-062 moved the transport into peat-mesh. The full peer-config loader
//! (TOML parsing, validation, etc.) remains in peat-protocol and re-exports
//! this struct so existing call sites keep working.

#[cfg(feature = "automerge-backend")]
use anyhow::{Context, Result};
#[cfg(feature = "automerge-backend")]
use iroh::EndpointId;
#[cfg(feature = "automerge-backend")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "automerge-backend")]
use std::net::SocketAddr;

/// A statically-configured peer that `IrohTransport::connect_peer` can dial.
#[cfg(feature = "automerge-backend")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerInfo {
    /// Human-readable peer name
    pub name: String,
    /// Iroh PublicKey (EndpointId) in hex format
    pub node_id: String,
    /// Direct addresses to try connecting to
    pub addresses: Vec<String>,
    /// Optional relay URL
    pub relay_url: Option<String>,
}

#[cfg(feature = "automerge-backend")]
impl PeerInfo {
    /// Parse `node_id` (hex string) as an iroh [`EndpointId`].
    pub fn endpoint_id(&self) -> Result<EndpointId> {
        let bytes = hex::decode(&self.node_id).context("Failed to decode node_id hex")?;
        if bytes.len() != 32 {
            anyhow::bail!(
                "Invalid node_id length: expected 32 bytes, got {}",
                bytes.len()
            );
        }
        let mut array = [0u8; 32];
        array.copy_from_slice(&bytes);
        EndpointId::from_bytes(&array).context("Failed to construct EndpointId from bytes")
    }

    /// Parse `addresses` as a list of [`SocketAddr`]s.
    pub fn socket_addrs(&self) -> Result<Vec<SocketAddr>> {
        self.addresses
            .iter()
            .map(|addr| {
                addr.parse()
                    .with_context(|| format!("Invalid address: {}", addr))
            })
            .collect()
    }
}

#[cfg(all(test, feature = "automerge-backend"))]
mod tests {
    use super::*;

    fn peer(node_id: &str, addrs: &[&str]) -> PeerInfo {
        PeerInfo {
            name: "test".to_string(),
            node_id: node_id.to_string(),
            addresses: addrs.iter().map(|s| s.to_string()).collect(),
            relay_url: None,
        }
    }

    /// Non-hex `node_id` is the most common static-peer-config typo. The error
    /// must name the hex-decode failure so operators can find the bad TOML line.
    #[test]
    fn endpoint_id_rejects_non_hex_node_id() {
        let p = peer("not hex at all!!", &[]);
        let err = p.endpoint_id().expect_err("non-hex node_id must error");
        let msg = format!("{err:#}");
        assert!(
            msg.contains("hex"),
            "error must mention hex decoding: got `{msg}`"
        );
    }

    /// Hex that decodes but is the wrong length is the second-most-common
    /// static-peer-config typo (truncated paste). The bail message must name
    /// the length mismatch.
    #[test]
    fn endpoint_id_rejects_wrong_length_node_id() {
        let p = peer("deadbeef", &[]); // 4 bytes, not 32
        let err = p.endpoint_id().expect_err("4-byte node_id must error");
        let msg = format!("{err:#}");
        assert!(
            msg.contains("32 bytes"),
            "error must name the expected length: got `{msg}`"
        );
    }

    /// Malformed `addresses` entry must surface as a parse error naming the
    /// offending address — `socket_addrs()` is the choke point for static
    /// peer-config TOML loading.
    #[test]
    fn socket_addrs_rejects_invalid_address() {
        let p = peer("00".repeat(32).as_str(), &["not-a-socket-addr"]);
        let err = p.socket_addrs().expect_err("malformed address must error");
        let msg = format!("{err:#}");
        assert!(
            msg.contains("not-a-socket-addr"),
            "error must name the bad address: got `{msg}`"
        );
    }
}
