use crate::*;
use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
use serde::*;

////////////////////////////////////////////////////////////////////////////////////////////////
pub type ConfigCallbackReturn = Result<Box<dyn core::any::Any + Send>, VeilidAPIError>;
pub type ConfigCallback = Arc<dyn Fn(String) -> ConfigCallbackReturn + Send + Sync>;

/// Enable and configure HTTPS access to the Veilid node
///
/// ```yaml
/// https:
///     enabled: false
///     listen_address: ':5150'
///     path: 'app'
///     url: 'https://localhost:5150'
/// ```
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigHTTPS {
    pub enabled: bool,
    pub listen_address: String,
    pub path: String,
    pub url: Option<String>, // Fixed URL is not optional for TLS-based protocols and is dynamically validated
}

/// Enable and configure HTTP access to the Veilid node
///
/// ```yaml
/// http:
///     enabled: false
///     listen_address: ':5150'
///     path: 'app"
///     url: 'https://localhost:5150'
/// ```
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigHTTP {
    pub enabled: bool,
    pub listen_address: String,
    pub path: String,
    pub url: Option<String>,
}

/// Application configuration
///
/// Configure web access to the Prograssive Web App (PWA)
///
/// To be implemented...
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigApplication {
    pub https: VeilidConfigHTTPS,
    pub http: VeilidConfigHTTP,
}

/// Enable and configure UDP
///
/// ```yaml
/// udp:
///     enabled: true
///     socket_pool_size: 0
///     listen_address: ':5150'
///     public_address: ''
/// ```
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigUDP {
    pub enabled: bool,
    pub socket_pool_size: u32,
    pub listen_address: String,
    pub public_address: Option<String>,
}

/// Enable and configure TCP
///
/// ```yaml
/// tcp:
///     connect: true
///     listen: true
///     max_connections: 32
///     listen_address: ':5150'
///     public_address: ''
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigTCP {
    pub connect: bool,
    pub listen: bool,
    pub max_connections: u32,
    pub listen_address: String,
    pub public_address: Option<String>,
}

/// Enable and configure Web Sockets
///
/// ```yaml
/// ws:
///     connect: true
///     listen: true
///     max_connections: 16
///     listen_address: ':5150'
///     path: 'ws'
///     url: 'ws://localhost:5150/ws'
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigWS {
    pub connect: bool,
    pub listen: bool,
    pub max_connections: u32,
    pub listen_address: String,
    pub path: String,
    pub url: Option<String>,
}

/// Enable and configure Secure Web Sockets
///
/// ```yaml
/// wss:
///     connect: true
///     listen: false
///     max_connections: 16
///     listen_address: ':5150'
///     path: 'ws'
///     url: ''
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigWSS {
    pub connect: bool,
    pub listen: bool,
    pub max_connections: u32,
    pub listen_address: String,
    pub path: String,
    pub url: Option<String>, // Fixed URL is not optional for TLS-based protocols and is dynamically validated
}

/// Configure Network Protocols
///
/// Veilid can communicate over UDP, TCP, and Web Sockets.
///
/// All protocols are available by default, and the Veilid node will
/// sort out which protocol is used for each peer connection.
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigProtocol {
    pub udp: VeilidConfigUDP,
    pub tcp: VeilidConfigTCP,
    pub ws: VeilidConfigWS,
    pub wss: VeilidConfigWSS,
}

/// Configure TLS
///
/// ```yaml
/// tls:
///     certificate_path: /path/to/cert
///     private_key_path: /path/to/private/key
///     connection_initial_timeout_ms: 2000
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigTLS {
    pub certificate_path: String,
    pub private_key_path: String,
    pub connection_initial_timeout_ms: u32,
}

/// Configure the Distributed Hash Table (DHT)
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigDHT {
    pub resolve_node_timeout_ms: Option<u32>,
    pub resolve_node_count: u32,
    pub resolve_node_fanout: u32,
    pub max_find_node_count: u32,
    pub get_value_timeout_ms: Option<u32>,
    pub get_value_count: u32,
    pub get_value_fanout: u32,
    pub set_value_timeout_ms: Option<u32>,
    pub set_value_count: u32,
    pub set_value_fanout: u32,
    pub min_peer_count: u32,
    pub min_peer_refresh_time_ms: u32,
    pub validate_dial_info_receipt_time_ms: u32,
}

/// Configure RPC
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigRPC {
    pub concurrency: u32,
    pub queue_size: u32,
    pub max_timestamp_behind_ms: Option<u32>,
    pub max_timestamp_ahead_ms: Option<u32>,
    pub timeout_ms: u32,
    pub max_route_hop_count: u8,
    pub default_route_hop_count: u8,
}

/// Configure the per-crypto version configuration
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigNodeId {
    pub node_id: Option<PublicKey>,
    pub node_id_secret: Option<SecretKey>,
}

/// Configure the network routing table
///
#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigRoutingTable {
    pub node_ids: BTreeMap<CryptoKind, VeilidConfigNodeId>,
    pub bootstrap: Vec<String>,
    pub limit_over_attached: u32,
    pub limit_fully_attached: u32,
    pub limit_attached_strong: u32,
    pub limit_attached_good: u32,
    pub limit_attached_weak: u32,
    // xxx pub enable_public_internet: bool,
    // xxx pub enable_local_network: bool,
}

#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigNetwork {
    pub connection_initial_timeout_ms: u32,
    pub connection_inactivity_timeout_ms: u32,
    pub max_connections_per_ip4: u32,
    pub max_connections_per_ip6_prefix: u32,
    pub max_connections_per_ip6_prefix_size: u32,
    pub max_connection_frequency_per_min: u32,
    pub client_whitelist_timeout_ms: u32,
    pub reverse_connection_receipt_time_ms: u32,
    pub hole_punch_receipt_time_ms: u32,
    pub routing_table: VeilidConfigRoutingTable,
    pub rpc: VeilidConfigRPC,
    pub dht: VeilidConfigDHT,
    pub upnp: bool,
    pub detect_address_changes: bool,
    pub restricted_nat_retries: u32,
    pub tls: VeilidConfigTLS,
    pub application: VeilidConfigApplication,
    pub protocol: VeilidConfigProtocol,
}

#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigTableStore {
    pub directory: String,
    pub delete: bool,
}

#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigBlockStore {
    pub directory: String,
    pub delete: bool,
}

#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigProtectedStore {
    pub allow_insecure_fallback: bool,
    pub always_use_insecure_storage: bool,
    pub insecure_fallback_directory: String,
    pub delete: bool,
}

#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigCapabilities {
    pub protocol_udp: bool,
    pub protocol_connect_tcp: bool,
    pub protocol_accept_tcp: bool,
    pub protocol_connect_ws: bool,
    pub protocol_accept_ws: bool,
    pub protocol_connect_wss: bool,
    pub protocol_accept_wss: bool,
}

#[derive(
    Clone,
    Copy,
    PartialEq,
    Eq,
    Debug,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub enum VeilidConfigLogLevel {
    Off,
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl VeilidConfigLogLevel {
    pub fn to_veilid_log_level(&self) -> Option<VeilidLogLevel> {
        match self {
            Self::Off => None,
            Self::Error => Some(VeilidLogLevel::Error),
            Self::Warn => Some(VeilidLogLevel::Warn),
            Self::Info => Some(VeilidLogLevel::Info),
            Self::Debug => Some(VeilidLogLevel::Debug),
            Self::Trace => Some(VeilidLogLevel::Trace),
        }
    }
    pub fn to_tracing_level_filter(&self) -> level_filters::LevelFilter {
        match self {
            Self::Off => level_filters::LevelFilter::OFF,
            Self::Error => level_filters::LevelFilter::ERROR,
            Self::Warn => level_filters::LevelFilter::WARN,
            Self::Info => level_filters::LevelFilter::INFO,
            Self::Debug => level_filters::LevelFilter::DEBUG,
            Self::Trace => level_filters::LevelFilter::TRACE,
        }
    }
    pub fn from_veilid_log_level(level: Option<VeilidLogLevel>) -> Self {
        match level {
            None => Self::Off,
            Some(VeilidLogLevel::Error) => Self::Error,
            Some(VeilidLogLevel::Warn) => Self::Warn,
            Some(VeilidLogLevel::Info) => Self::Info,
            Some(VeilidLogLevel::Debug) => Self::Debug,
            Some(VeilidLogLevel::Trace) => Self::Trace,
        }
    }
    pub fn from_tracing_level_filter(level: level_filters::LevelFilter) -> Self {
        match level {
            level_filters::LevelFilter::OFF => Self::Off,
            level_filters::LevelFilter::ERROR => Self::Error,
            level_filters::LevelFilter::WARN => Self::Warn,
            level_filters::LevelFilter::INFO => Self::Info,
            level_filters::LevelFilter::DEBUG => Self::Debug,
            level_filters::LevelFilter::TRACE => Self::Trace,
        }
    }
}
impl Default for VeilidConfigLogLevel {
    fn default() -> Self {
        Self::Off
    }
}

#[derive(
    Default,
    Debug,
    Clone,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    RkyvArchive,
    RkyvSerialize,
    RkyvDeserialize,
)]
pub struct VeilidConfigInner {
    pub program_name: String,
    pub namespace: String,
    pub capabilities: VeilidConfigCapabilities,
    pub protected_store: VeilidConfigProtectedStore,
    pub table_store: VeilidConfigTableStore,
    pub block_store: VeilidConfigBlockStore,
    pub network: VeilidConfigNetwork,
}

/// The Veilid Configuration
///
/// Veilid is configured
#[derive(Clone)]
pub struct VeilidConfig {
    update_cb: Option<UpdateCallback>,
    inner: Arc<RwLock<VeilidConfigInner>>,
}

impl fmt::Debug for VeilidConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let inner = self.inner.read();
        f.debug_struct("VeilidConfig")
            .field("inner", &*inner)
            .finish()
    }
}

impl Default for VeilidConfig {
    fn default() -> Self {
        Self::new()
    }
}
impl VeilidConfig {
    fn new_inner() -> VeilidConfigInner {
        VeilidConfigInner::default()
    }

    pub fn new() -> Self {
        Self {
            update_cb: None,
            inner: Arc::new(RwLock::new(Self::new_inner())),
        }
    }

    pub fn setup_from_json(
        &mut self,
        config: String,
        update_cb: UpdateCallback,
    ) -> Result<(), VeilidAPIError> {
        self.update_cb = Some(update_cb);

        self.with_mut(|inner| {
            *inner = serde_json::from_str(&config).map_err(VeilidAPIError::generic)?;
            Ok(())
        })
    }

    pub fn setup(
        &mut self,
        cb: ConfigCallback,
        update_cb: UpdateCallback,
    ) -> Result<(), VeilidAPIError> {
        macro_rules! get_config {
            ($key:expr) => {
                let keyname = &stringify!($key)[6..];
                let v = cb(keyname.to_owned())?;
                $key = match v.downcast() {
                    Ok(v) => *v,
                    Err(_) => {
                        apibail_generic!(format!("incorrect type for key {}", keyname))
                    }
                };
            };
        }
        macro_rules! get_config_indexed {
            ($key:expr, $index:expr, $subkey:tt) => {
                let keyname = format!(
                    "{}[{}].{}",
                    &stringify!($key)[6..],
                    $index,
                    &stringify!($subkey)
                );
                let v = cb(keyname.to_owned())?;
                $key.entry($index).or_default().$subkey = match v.downcast() {
                    Ok(v) => *v,
                    Err(_) => {
                        apibail_generic!(format!("incorrect type for key {}", keyname))
                    }
                };
            };
        }

        self.update_cb = Some(update_cb);
        self.with_mut(|inner| {
            get_config!(inner.program_name);
            get_config!(inner.namespace);
            get_config!(inner.capabilities.protocol_udp);
            get_config!(inner.capabilities.protocol_connect_tcp);
            get_config!(inner.capabilities.protocol_accept_tcp);
            get_config!(inner.capabilities.protocol_connect_ws);
            get_config!(inner.capabilities.protocol_accept_ws);
            get_config!(inner.capabilities.protocol_connect_wss);
            get_config!(inner.capabilities.protocol_accept_wss);
            get_config!(inner.table_store.directory);
            get_config!(inner.table_store.delete);
            get_config!(inner.block_store.directory);
            get_config!(inner.block_store.delete);
            get_config!(inner.protected_store.allow_insecure_fallback);
            get_config!(inner.protected_store.always_use_insecure_storage);
            get_config!(inner.protected_store.insecure_fallback_directory);
            get_config!(inner.protected_store.delete);
            get_config!(inner.network.connection_initial_timeout_ms);
            get_config!(inner.network.connection_inactivity_timeout_ms);
            get_config!(inner.network.max_connections_per_ip4);
            get_config!(inner.network.max_connections_per_ip6_prefix);
            get_config!(inner.network.max_connections_per_ip6_prefix_size);
            get_config!(inner.network.max_connection_frequency_per_min);
            get_config!(inner.network.client_whitelist_timeout_ms);
            get_config!(inner.network.reverse_connection_receipt_time_ms);
            get_config!(inner.network.hole_punch_receipt_time_ms);
            for ck in VALID_CRYPTO_KINDS {
                get_config_indexed!(inner.network.routing_table.node_ids, ck, node_id);
                get_config_indexed!(inner.network.routing_table.node_ids, ck, node_id_secret);
            }
            get_config!(inner.network.routing_table.bootstrap);
            get_config!(inner.network.routing_table.limit_over_attached);
            get_config!(inner.network.routing_table.limit_fully_attached);
            get_config!(inner.network.routing_table.limit_attached_strong);
            get_config!(inner.network.routing_table.limit_attached_good);
            get_config!(inner.network.routing_table.limit_attached_weak);
            get_config!(inner.network.dht.resolve_node_timeout_ms);
            get_config!(inner.network.dht.resolve_node_count);
            get_config!(inner.network.dht.resolve_node_fanout);
            get_config!(inner.network.dht.max_find_node_count);
            get_config!(inner.network.dht.get_value_timeout_ms);
            get_config!(inner.network.dht.get_value_count);
            get_config!(inner.network.dht.get_value_fanout);
            get_config!(inner.network.dht.set_value_timeout_ms);
            get_config!(inner.network.dht.set_value_count);
            get_config!(inner.network.dht.set_value_fanout);
            get_config!(inner.network.dht.min_peer_count);
            get_config!(inner.network.dht.min_peer_refresh_time_ms);
            get_config!(inner.network.dht.validate_dial_info_receipt_time_ms);
            get_config!(inner.network.rpc.concurrency);
            get_config!(inner.network.rpc.queue_size);
            get_config!(inner.network.rpc.max_timestamp_behind_ms);
            get_config!(inner.network.rpc.max_timestamp_ahead_ms);
            get_config!(inner.network.rpc.timeout_ms);
            get_config!(inner.network.rpc.max_route_hop_count);
            get_config!(inner.network.rpc.default_route_hop_count);
            get_config!(inner.network.upnp);
            get_config!(inner.network.detect_address_changes);
            get_config!(inner.network.restricted_nat_retries);
            get_config!(inner.network.tls.certificate_path);
            get_config!(inner.network.tls.private_key_path);
            get_config!(inner.network.tls.connection_initial_timeout_ms);
            get_config!(inner.network.application.https.enabled);
            get_config!(inner.network.application.https.listen_address);
            get_config!(inner.network.application.https.path);
            get_config!(inner.network.application.https.url);
            get_config!(inner.network.application.http.enabled);
            get_config!(inner.network.application.http.listen_address);
            get_config!(inner.network.application.http.path);
            get_config!(inner.network.application.http.url);
            get_config!(inner.network.protocol.udp.enabled);
            get_config!(inner.network.protocol.udp.socket_pool_size);
            get_config!(inner.network.protocol.udp.listen_address);
            get_config!(inner.network.protocol.udp.public_address);
            get_config!(inner.network.protocol.tcp.connect);
            get_config!(inner.network.protocol.tcp.listen);
            get_config!(inner.network.protocol.tcp.max_connections);
            get_config!(inner.network.protocol.tcp.listen_address);
            get_config!(inner.network.protocol.tcp.public_address);
            get_config!(inner.network.protocol.ws.connect);
            get_config!(inner.network.protocol.ws.listen);
            get_config!(inner.network.protocol.ws.max_connections);
            get_config!(inner.network.protocol.ws.listen_address);
            get_config!(inner.network.protocol.ws.path);
            get_config!(inner.network.protocol.ws.url);
            get_config!(inner.network.protocol.wss.connect);
            get_config!(inner.network.protocol.wss.listen);
            get_config!(inner.network.protocol.wss.max_connections);
            get_config!(inner.network.protocol.wss.listen_address);
            get_config!(inner.network.protocol.wss.path);
            get_config!(inner.network.protocol.wss.url);
            Ok(())
        })
    }

    pub fn get_veilid_state(&self) -> VeilidStateConfig {
        let inner = self.inner.read();
        VeilidStateConfig {
            config: inner.clone(),
        }
    }

    pub fn get(&self) -> RwLockReadGuard<VeilidConfigInner> {
        self.inner.read()
    }

    pub fn with_mut<F, R>(&self, f: F) -> Result<R, VeilidAPIError>
    where
        F: FnOnce(&mut VeilidConfigInner) -> Result<R, VeilidAPIError>,
    {
        let (out, config) = {
            let inner = &mut *self.inner.write();
            // Edit a copy
            let mut editedinner = inner.clone();
            // Make changes
            let out = f(&mut editedinner)?;
            // Validate
            Self::validate(&mut editedinner)?;
            // Commit changes
            *inner = editedinner.clone();
            (out, editedinner)
        };

        // Send configuration update to clients
        if let Some(update_cb) = &self.update_cb {
            update_cb(VeilidUpdate::Config(VeilidStateConfig { config }));
        }

        Ok(out)
    }

    pub fn get_key_json(&self, key: &str) -> Result<String, VeilidAPIError> {
        let c = self.get();

        // Generate json from whole config
        let jc = serde_json::to_string(&*c).map_err(VeilidAPIError::generic)?;
        let jvc = json::parse(&jc).map_err(VeilidAPIError::generic)?;

        // Find requested subkey
        if key.is_empty() {
            Ok(jvc.to_string())
        } else {
            // Split key into path parts
            let keypath: Vec<&str> = key.split('.').collect();
            let mut out = &jvc;
            for k in keypath {
                if !out.has_key(k) {
                    apibail_parse_error!(format!("invalid subkey in key '{}'", key), k);
                }
                out = &out[k];
            }
            Ok(out.to_string())
        }
    }
    pub fn set_key_json(&self, key: &str, value: &str) -> Result<(), VeilidAPIError> {
        self.with_mut(|c| {
            // Split key into path parts
            let keypath: Vec<&str> = key.split('.').collect();

            // Convert value into jsonvalue
            let newval = json::parse(value).map_err(VeilidAPIError::generic)?;

            // Generate json from whole config
            let jc = serde_json::to_string(&*c).map_err(VeilidAPIError::generic)?;
            let mut jvc = json::parse(&jc).map_err(VeilidAPIError::generic)?;

            // Find requested subkey
            let newconfigstring = if let Some((objkeyname, objkeypath)) = keypath.split_last() {
                // Replace subkey
                let mut out = &mut jvc;
                for k in objkeypath {
                    if !out.has_key(*k) {
                        apibail_parse_error!(format!("invalid subkey in key '{}'", key), k);
                    }
                    out = &mut out[*k];
                }
                if !out.has_key(objkeyname) {
                    apibail_parse_error!(format!("invalid subkey in key '{}'", key), objkeyname);
                }
                out[*objkeyname] = newval;
                jvc.to_string()
            } else {
                newval.to_string()
            };

            // Generate new config
            *c = serde_json::from_str(&newconfigstring).map_err(VeilidAPIError::generic)?;
            Ok(())
        })
    }

    fn validate(inner: &VeilidConfigInner) -> Result<(), VeilidAPIError> {
        if inner.program_name.is_empty() {
            apibail_generic!("Program name must not be empty in 'program_name'");
        }

        // if inner.network.protocol.udp.enabled {
        //     // Validate UDP settings
        // }
        if inner.network.protocol.tcp.listen {
            // Validate TCP settings
            if inner.network.protocol.tcp.max_connections == 0 {
                apibail_generic!("TCP max connections must be > 0 in config key 'network.protocol.tcp.max_connections'");
            }
        }
        if inner.network.protocol.ws.listen {
            // Validate WS settings
            if inner.network.protocol.ws.max_connections == 0 {
                apibail_generic!("WS max connections must be > 0 in config key 'network.protocol.ws.max_connections'");
            }
            if inner.network.application.https.enabled
                && inner.network.application.https.path == inner.network.protocol.ws.path
            {
                apibail_generic!("WS path conflicts with HTTPS application path in config key 'network.protocol.ws.path'");
            }
            if inner.network.application.http.enabled
                && inner.network.application.http.path == inner.network.protocol.ws.path
            {
                apibail_generic!("WS path conflicts with HTTP application path in config key 'network.protocol.ws.path'");
            }
        }
        if inner.network.protocol.wss.listen {
            // Validate WSS settings
            if inner.network.protocol.wss.max_connections == 0 {
                apibail_generic!("WSS max connections must be > 0 in config key 'network.protocol.wss.max_connections'");
            }
            if inner
                .network
                .protocol
                .wss
                .url
                .as_ref()
                .map(|u| u.is_empty())
                .unwrap_or_default()
            {
                apibail_generic!(
                    "WSS URL must be specified in config key 'network.protocol.wss.url'"
                );
            }
            if inner.network.application.https.enabled
                && inner.network.application.https.path == inner.network.protocol.wss.path
            {
                apibail_generic!("WSS path conflicts with HTTPS application path in config key 'network.protocol.ws.path'");
            }
            if inner.network.application.http.enabled
                && inner.network.application.http.path == inner.network.protocol.wss.path
            {
                apibail_generic!("WSS path conflicts with HTTP application path in config key 'network.protocol.ws.path'");
            }
        }
        if inner.network.application.https.enabled {
            // Validate HTTPS settings
            if inner
                .network
                .application
                .https
                .url
                .as_ref()
                .map(|u| u.is_empty())
                .unwrap_or_default()
            {
                apibail_generic!(
                    "HTTPS URL must be specified in config key 'network.application.https.url'"
                );
            }
        }
        if inner.network.rpc.max_route_hop_count == 0 {
            apibail_generic!(
                "max route hop count must be >= 1 in 'network.rpc.max_route_hop_count'"
            );
        }
        if inner.network.rpc.max_route_hop_count > 5 {
            apibail_generic!(
                "max route hop count must be <= 5 in 'network.rpc.max_route_hop_count'"
            );
        }
        if inner.network.rpc.default_route_hop_count == 0 {
            apibail_generic!(
                "default route hop count must be >= 1 in 'network.rpc.default_route_hop_count'"
            );
        }
        if inner.network.rpc.default_route_hop_count > inner.network.rpc.max_route_hop_count {
            apibail_generic!(
                "default route hop count must be <= max route hop count in 'network.rpc.default_route_hop_count <= network.rpc.max_route_hop_count'"
            );
        }
        if inner.network.rpc.queue_size < 256 {
            apibail_generic!("rpc queue size must be >= 256 in 'network.rpc.queue_size'");
        }
        if inner.network.rpc.timeout_ms < 1000 {
            apibail_generic!("rpc timeout must be >= 1000 in 'network.rpc.timeout_ms'");
        }

        Ok(())
    }

    // Get the node id from config if one is specified
    // Must be done -after- protected store startup
    pub async fn init_node_ids(
        &self,
        crypto: Crypto,
        protected_store: intf::ProtectedStore,
    ) -> Result<(), VeilidAPIError> {
        for ck in VALID_CRYPTO_KINDS {
            let vcrypto = crypto
                .get(ck)
                .expect("Valid crypto kind is not actually valid.");

            let mut node_id = self
                .inner
                .read()
                .network
                .routing_table
                .node_ids
                .get(&ck)
                .map(|n| n.node_id)
                .flatten();
            let mut node_id_secret = self
                .inner
                .read()
                .network
                .routing_table
                .node_ids
                .get(&ck)
                .map(|n| n.node_id_secret)
                .flatten();
            // See if node id was previously stored in the protected store
            if node_id.is_none() {
                debug!("pulling node_id_{} from storage", ck);
                if let Some(s) = protected_store
                    .load_user_secret_string(format!("node_id_{}", ck))
                    .await
                    .map_err(VeilidAPIError::internal)?
                {
                    debug!("node_id_{} found in storage", ck);
                    node_id =
                        Some(PublicKey::try_decode(s.as_str()).map_err(VeilidAPIError::internal)?);
                } else {
                    debug!("node_id_{} not found in storage", ck);
                }
            }

            // See if node id secret was previously stored in the protected store
            if node_id_secret.is_none() {
                debug!("pulling node id secret from storage");
                if let Some(s) = protected_store
                    .load_user_secret_string(format!("node_id_secret_{}", ck))
                    .await
                    .map_err(VeilidAPIError::internal)?
                {
                    debug!("node_id_secret_{} found in storage", ck);
                    node_id_secret =
                        Some(SecretKey::try_decode(s.as_str()).map_err(VeilidAPIError::internal)?);
                } else {
                    debug!("node_id_secret_{} not found in storage", ck);
                }
            }

            // If we have a node id from storage, check it
            let (node_id, node_id_secret) =
                if let (Some(node_id), Some(node_id_secret)) = (node_id, node_id_secret) {
                    // Validate node id
                    if !vcrypto.validate_keypair(&node_id, &node_id_secret) {
                        apibail_generic!(format!(
                            "node_id_secret_{} and node_id_key_{} don't match",
                            ck, ck
                        ));
                    }
                    (node_id, node_id_secret)
                } else {
                    // If we still don't have a valid node id, generate one
                    debug!("generating new node_id_{}", ck);
                    vcrypto.generate_keypair()
                };
            info!("Node Id ({}) is {}", ck, node_id.encode());

            // Save the node id / secret in storage
            protected_store
                .save_user_secret_string(format!("node_id_{}", ck), node_id.encode().as_str())
                .await
                .map_err(VeilidAPIError::internal)?;
            protected_store
                .save_user_secret_string(
                    format!("node_id_secret_{}", ck),
                    node_id_secret.encode().as_str(),
                )
                .await
                .map_err(VeilidAPIError::internal)?;

            self.with_mut(|c| {
                let n = c.network.routing_table.node_ids.entry(ck).or_default();
                n.node_id = Some(node_id);
                n.node_id_secret = Some(node_id_secret);
                Ok(())
            })?;
        }
        trace!("init_node_ids complete");

        Ok(())
    }
}