//! Network configuration.

use crate::{ChainId, DEFAULT_PORT, MAX_PEERS};
use libp2p::Multiaddr;
use std::time::Duration;

/// Network configuration.
#[derive(Clone, Debug)]
pub struct NetworkConfig {
    /// Chain identifier.
    pub chain_id: ChainId,
    /// Listen addresses.
    pub listen_addresses: Vec<Multiaddr>,
    /// Bootstrap peers.
    pub bootstrap_peers: Vec<Multiaddr>,
    /// Maximum number of inbound connections.
    pub max_inbound: usize,
    /// Maximum number of outbound connections.
    pub max_outbound: usize,
    /// Enable mDNS for local peer discovery.
    pub enable_mdns: bool,
    /// Enable Kademlia DHT.
    pub enable_kad: bool,
    /// Connection idle timeout.
    pub idle_timeout: Duration,
    /// Ping interval.
    pub ping_interval: Duration,
    /// Gossipsub configuration.
    pub gossipsub: GossipsubConfig,
    /// Sync configuration.
    pub sync: SyncConfig,
    /// External address (for NAT traversal).
    pub external_address: Option<Multiaddr>,
    /// Node name for identification.
    pub node_name: Option<String>,
}

impl Default for NetworkConfig {
    fn default() -> Self {
        NetworkConfig {
            chain_id: ChainId::Mainnet,
            listen_addresses: vec![
                format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT)
                    .parse()
                    .unwrap(),
                format!("/ip6/::/tcp/{}", DEFAULT_PORT).parse().unwrap(),
            ],
            bootstrap_peers: Vec::new(),
            max_inbound: MAX_PEERS / 2,
            max_outbound: MAX_PEERS / 2,
            enable_mdns: true,
            enable_kad: true,
            idle_timeout: Duration::from_secs(30),
            ping_interval: Duration::from_secs(15),
            gossipsub: GossipsubConfig::default(),
            sync: SyncConfig::default(),
            external_address: None,
            node_name: None,
        }
    }
}

impl NetworkConfig {
    /// Creates a configuration for mainnet.
    pub fn mainnet() -> Self {
        NetworkConfig {
            chain_id: ChainId::Mainnet,
            bootstrap_peers: mainnet_bootstrap_peers(),
            enable_mdns: false,
            ..Default::default()
        }
    }

    /// Creates a configuration for testnet.
    pub fn testnet() -> Self {
        NetworkConfig {
            chain_id: ChainId::Testnet,
            bootstrap_peers: testnet_bootstrap_peers(),
            listen_addresses: vec![format!("/ip4/0.0.0.0/tcp/{}", DEFAULT_PORT + 1000)
                .parse()
                .unwrap()],
            ..Default::default()
        }
    }

    /// Creates a configuration for local development.
    pub fn devnet() -> Self {
        NetworkConfig {
            chain_id: ChainId::Devnet,
            enable_mdns: true,
            enable_kad: false,
            max_inbound: 10,
            max_outbound: 10,
            ..Default::default()
        }
    }

    /// Total maximum peers.
    pub fn max_peers(&self) -> usize {
        self.max_inbound + self.max_outbound
    }

    /// Adds a bootstrap peer.
    pub fn with_bootstrap_peer(mut self, addr: Multiaddr) -> Self {
        self.bootstrap_peers.push(addr);
        self
    }

    /// Sets the listen address.
    pub fn with_listen_address(mut self, addr: Multiaddr) -> Self {
        self.listen_addresses = vec![addr];
        self
    }

    /// Sets the node name.
    pub fn with_node_name(mut self, name: impl Into<String>) -> Self {
        self.node_name = Some(name.into());
        self
    }
}

/// GossipSub configuration.
#[derive(Clone, Debug)]
pub struct GossipsubConfig {
    /// Heartbeat interval.
    pub heartbeat_interval: Duration,
    /// Target mesh size.
    pub mesh_n: usize,
    /// Minimum mesh size.
    pub mesh_n_low: usize,
    /// Maximum mesh size.
    pub mesh_n_high: usize,
    /// Number of lazy peers to send gossip.
    pub gossip_lazy: usize,
    /// Fanout time-to-live.
    pub fanout_ttl: Duration,
    /// History length for gossip.
    pub history_length: usize,
    /// History gossip length.
    pub history_gossip: usize,
    /// Duplicate cache time.
    pub duplicate_cache_time: Duration,
    /// Message validation mode.
    pub validate_messages: bool,
}

impl Default for GossipsubConfig {
    fn default() -> Self {
        GossipsubConfig {
            heartbeat_interval: Duration::from_secs(1),
            mesh_n: 6,
            mesh_n_low: 4,
            mesh_n_high: 12,
            gossip_lazy: 6,
            fanout_ttl: Duration::from_secs(60),
            history_length: 5,
            history_gossip: 3,
            duplicate_cache_time: Duration::from_secs(60),
            validate_messages: true,
        }
    }
}

/// Synchronization configuration.
#[derive(Clone, Debug)]
pub struct SyncConfig {
    /// Maximum concurrent block downloads.
    pub max_concurrent_downloads: usize,
    /// Block request timeout.
    pub request_timeout: Duration,
    /// Maximum blocks per request.
    pub max_blocks_per_request: usize,
    /// Sync batch size.
    pub batch_size: usize,
    /// Time to wait before retrying failed requests.
    pub retry_delay: Duration,
    /// Maximum retries for failed requests.
    pub max_retries: u32,
    /// Header-first sync depth.
    pub headers_first_depth: u64,
}

impl Default for SyncConfig {
    fn default() -> Self {
        SyncConfig {
            max_concurrent_downloads: 16,
            request_timeout: Duration::from_secs(30),
            max_blocks_per_request: 100,
            batch_size: 500,
            retry_delay: Duration::from_secs(5),
            max_retries: 3,
            headers_first_depth: 10000,
        }
    }
}

/// Returns mainnet bootstrap peers.
fn mainnet_bootstrap_peers() -> Vec<Multiaddr> {
    // Mainnet bootstrap nodes - will be populated after mainnet launch
    // Format: /dns4/<hostname>/tcp/<port>/p2p/<peer_id>
    vec![
        // Example (uncomment when deployed):
        // "/dns4/seed1.synor.cc/tcp/16511/p2p/12D3KooW...".parse().unwrap(),
        // "/dns4/seed2.synor.cc/tcp/16511/p2p/12D3KooW...".parse().unwrap(),
        // "/dns4/seed3.synor.cc/tcp/16511/p2p/12D3KooW...".parse().unwrap(),
    ]
}

/// Returns testnet bootstrap peers.
///
/// # Seed Node Deployment Process
///
/// To add a new seed node:
///
/// 1. **Deploy synord on a server** with a static IP/hostname:
///    ```bash
///    synord --network testnet --rpc-host 0.0.0.0
///    ```
///
/// 2. **Get the peer ID** from startup logs:
///    ```
///    INFO synor_network::service: Local peer ID: 12D3KooWAbCdEfGhIjKlMnOpQrStUvWxYz123456789
///    ```
///
/// 3. **Add the full multiaddr** (hostname + port + peer_id):
///    ```text
///    /dns4/testnet-seed1.synor.cc/tcp/17511/p2p/12D3KooWAbCdEfGhIjKlMnOpQrStUvWxYz123456789
///    ```
///
/// # Runtime Configuration
///
/// Instead of hardcoding, operators can use the `SYNOR_BOOTSTRAP_PEERS` environment
/// variable at the synord application level (comma-separated multiaddrs).
fn testnet_bootstrap_peers() -> Vec<Multiaddr> {
    // Testnet bootstrap nodes - add peer IDs when seed nodes are deployed
    // Format: /dns4/<hostname>/tcp/<port>/p2p/<peer_id>
    //
    // NOTE: Seeds are configured empty here because peer IDs are only known
    // after deployment. Use SYNOR_BOOTSTRAP_PEERS env var or config file
    // to specify bootstrap peers at runtime.
    let seeds: &[&str] = &[
        // North America (seed1.synor.cc) - uncomment after deployment:
        // "/dns4/testnet-seed1.synor.cc/tcp/17511/p2p/12D3KooW...",
        // Europe (seed2.synor.cc) - uncomment after deployment:
        // "/dns4/testnet-seed2.synor.cc/tcp/17511/p2p/12D3KooW...",
        // Asia (seed3.synor.cc) - uncomment after deployment:
        // "/dns4/testnet-seed3.synor.cc/tcp/17511/p2p/12D3KooW...",
    ];

    seeds.iter().filter_map(|s| s.parse().ok()).collect()
}

/// Returns devnet bootstrap peers for local development.
///
/// For local multi-node testing, start the first node and note its peer ID,
/// then configure other nodes to bootstrap from it.
fn devnet_bootstrap_peers() -> Vec<Multiaddr> {
    // Local devnet typically uses mDNS for discovery
    // Add local bootstrap peers here if needed for testing without mDNS
    vec![]
}

/// Testnet network parameters.
pub mod testnet_params {
    /// Testnet chain ID.
    pub const CHAIN_ID: u64 = 1;
    /// Target block time in milliseconds.
    pub const BLOCK_TIME_MS: u64 = 100;
    /// GHOSTDAG K parameter.
    pub const GHOSTDAG_K: u32 = 18;
    /// Default P2P port for testnet.
    pub const DEFAULT_PORT: u16 = 17511;
    /// Default RPC port for testnet.
    pub const RPC_PORT: u16 = 17110;
    /// Default WebSocket port for testnet.
    pub const WS_PORT: u16 = 17111;
}

/// Mainnet network parameters.
pub mod mainnet_params {
    /// Mainnet chain ID.
    pub const CHAIN_ID: u64 = 0;
    /// Target block time in milliseconds.
    pub const BLOCK_TIME_MS: u64 = 1000;
    /// GHOSTDAG K parameter.
    pub const GHOSTDAG_K: u32 = 18;
    /// Default P2P port for mainnet.
    pub const DEFAULT_PORT: u16 = 16511;
    /// Default RPC port for mainnet.
    pub const RPC_PORT: u16 = 16110;
    /// Default WebSocket port for mainnet.
    pub const WS_PORT: u16 = 16111;
}

/// Devnet network parameters for local development and testing.
pub mod devnet_params {
    /// Devnet chain ID.
    pub const CHAIN_ID: u64 = 2;
    /// Target block time in milliseconds (faster for testing).
    pub const BLOCK_TIME_MS: u64 = 50;
    /// GHOSTDAG K parameter (smaller for faster consensus).
    pub const GHOSTDAG_K: u32 = 8;
    /// Default P2P port for devnet.
    pub const DEFAULT_PORT: u16 = 18511;
    /// Default RPC port for devnet.
    pub const RPC_PORT: u16 = 18110;
    /// Default WebSocket port for devnet.
    pub const WS_PORT: u16 = 18111;
}

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

    #[test]
    fn test_default_config() {
        let config = NetworkConfig::default();
        assert_eq!(config.chain_id, ChainId::Mainnet);
        assert!(config.max_peers() > 0);
    }

    #[test]
    fn test_testnet_config() {
        let config = NetworkConfig::testnet();
        assert_eq!(config.chain_id, ChainId::Testnet);
    }

    #[test]
    fn test_devnet_config() {
        let config = NetworkConfig::devnet();
        assert!(config.enable_mdns);
        assert!(!config.enable_kad);
    }
}
