//! The binary kitsune2-bootstrap-srv.

use kitsune2_bootstrap_srv::*;

#[derive(clap::Parser, Debug)]
#[command(version)]
pub struct Args {
    /// By default, kitsune2-boot-srv runs in "testing" configuration
    /// with much lighter resource usage settings. This testing mode
    /// should be more than enough for most developer application testing
    /// and continuous integration or automated tests.
    ///
    /// To set up the server to be ready to use most of the resources available
    /// on a single given machine, you can set this "production" mode.
    #[arg(long)]
    pub production: bool,

    /// Output tracing in json format.
    #[arg(long)]
    pub json: bool,

    /// The address(es) at which to listen.
    ///
    /// Defaults: testing = "[127.0.0.1:0]", production = "[0.0.0.0:443, [::]:443]"
    #[arg(long)]
    pub listen: Vec<std::net::SocketAddr>,

    /// The path to a TLS certificate file.
    ///
    /// The certificate must be PEM encoded.
    #[arg(long, requires = "tls_key")]
    pub tls_cert: Option<std::path::PathBuf>,

    /// The path to a TLS key file.
    ///
    /// The key must be PEM encoded.
    #[arg(long, requires = "tls_cert")]
    pub tls_key: Option<std::path::PathBuf>,

    /// The number of worker threads to use.
    ///
    /// Defaults: testing = 2, production = 4 * cpu_count
    #[arg(long)]
    pub worker_thread_count: Option<usize>,

    /// The maximum agent info entry count per space.
    ///
    /// Defaults: testing = 32, production = 32
    #[arg(long)]
    pub max_entries_per_space: Option<usize>,

    /// The number of milliseconds that worker threads will block waiting for incoming connections
    /// before checking to see if the server is shutting down.
    ///
    /// Defaults: testing = 10ms, production = 2s
    #[arg(long)]
    pub request_listen_duration_ms: Option<u32>,

    /// The interval at which expired agents are purged from the cache.
    ///
    /// Defaults: testing = 10s, production = 60s
    #[arg(long)]
    pub prune_interval_ms: Option<u32>,

    /// The allowed origins for CORS requests.
    ///
    /// If `None`, defaults to allowing any origin.
    #[arg(long)]
    pub allowed_origins: Option<Vec<String>>,

    /// If specified, this server will only handle bootstrap requests,
    /// dropping websocket upgrade requests from sbd clients.
    #[arg(long)]
    pub no_sbd: bool,

    /// Use this http header to determine IP address instead of the raw
    /// TCP connection details.
    #[arg(long)]
    pub sbd_trusted_ip_header: Option<String>,

    /// Limit client connections.
    #[arg(long)]
    pub sbd_limit_clients: Option<i32>,

    /// If set, rate-limiting will be disabled on the server,
    /// and clients will be informed they have an 8 gbps rate limit.
    ///
    /// Note that this is an SBD option, but when SBD is enabled, this applies to all connections.
    #[arg(long)]
    pub sbd_disable_rate_limiting: bool,

    /// Rate limit connections to this number of kilobits per second.
    ///
    /// If not set, the default is decided by the SBD code.
    #[arg(long)]
    pub sbd_limit_ip_kbps: Option<i32>,

    /// Allow IPs to burst by this byte count.
    ///
    /// If not set, the default is decided by the SBD code.
    #[arg(long)]
    pub sbd_limit_ip_byte_burst: Option<i32>,

    /// If specified, this will enable exporting metrics to an OpenTelemetry endpoint.
    #[arg(long)]
    pub otlp_endpoint: Option<String>,

    /// The authentication "Hook Server" as defined by
    /// <https://github.com/holochain/sbd/blob/main/spec-auth.md>
    #[arg(long)]
    pub authentication_hook_server: Option<String>,

    /// The address on which the QUIC Address Discovery (QAD) server should bind.
    ///
    /// QAD allows iroh clients to discover their public IP address via a QUIC
    /// connection. When TLS cert/key files are configured they are used for
    /// QUIC; otherwise a self-signed certificate is generated for local
    /// development and testing.
    ///
    /// Default port is 7842 (iroh's default for QAD).
    #[arg(long)]
    pub quic_bind_addr: Option<std::net::SocketAddr>,
}

fn main() {
    let args = <Args as clap::Parser>::parse();

    let t = tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::builder()
                .with_default_directive(tracing::Level::DEBUG.into())
                .from_env_lossy(),
        )
        .with_file(true)
        .with_line_number(true);

    if args.json {
        t.json().try_init()
    } else {
        t.try_init()
    }
    .expect("failed to init tracing");

    let mut config = if args.production {
        Config::production()
    } else {
        Config::testing()
    };

    // Install the crypto provider if TLS certs are provided, or if iroh-relay
    // feature is enabled (iroh-relay uses rustls internally and needs the provider).
    if args.tls_cert.is_some()
        || args.tls_key.is_some()
        || cfg!(feature = "iroh-relay")
    {
        rustls::crypto::ring::default_provider()
            .install_default()
            .expect("Failed to configure default TLS provider");
    }

    // Apply bootstrap command line arguments
    config.tls_cert = args.tls_cert;
    config.tls_key = args.tls_key;
    if !args.listen.is_empty() {
        config.listen_address_list = args.listen;
    }
    if let Some(count) = args.worker_thread_count {
        config.worker_thread_count = count;
    }
    if let Some(count) = args.max_entries_per_space {
        config.max_entries_per_space = count;
    }
    if let Some(ms) = args.request_listen_duration_ms {
        config.request_listen_duration =
            std::time::Duration::from_millis(ms as u64);
    }
    if let Some(ms) = args.prune_interval_ms {
        config.prune_interval = std::time::Duration::from_millis(ms as u64);
    }
    if let Some(allowed_origins) = args.allowed_origins {
        config.allowed_origins = Some(allowed_origins);
    }

    #[cfg(feature = "sbd")]
    {
        // Apply SBD command line arguments
        if let Some(header) = args.sbd_trusted_ip_header {
            config.sbd.trusted_ip_header = Some(header);
        }
        if let Some(limit) = args.sbd_limit_clients {
            config.sbd.limit_clients = limit;
        }
        if args.sbd_disable_rate_limiting {
            config.sbd.disable_rate_limiting = true;
        }
        if let Some(kbps) = args.sbd_limit_ip_kbps {
            config.sbd.limit_ip_kbps = kbps;
        }
        if let Some(byte_burst) = args.sbd_limit_ip_byte_burst {
            config.sbd.limit_ip_byte_burst = byte_burst;
        }
        config.sbd.otlp_endpoint = args.otlp_endpoint;
        config.sbd.authentication_hook_server =
            args.authentication_hook_server.clone();

        // Setup opentelemetry metrics
        sbd_server::enable_otlp_metrics_if_configured(&config.sbd)
            .expect("Failed to initialize OTLP metrics");
    }

    #[cfg(feature = "iroh-relay")]
    {
        if let Some(quic_bind_addr) = args.quic_bind_addr {
            config.quic_bind_addr = Some(quic_bind_addr);
        }
    }

    // Set auth in the new feature-independent location
    config.auth.authentication_hook_server = args.authentication_hook_server;

    tracing::info!(?config);

    let (send, recv) = std::sync::mpsc::channel();

    ctrlc::set_handler(move || {
        send.send(()).unwrap();
    })
    .unwrap();

    let srv = BootstrapSrv::new(config);

    if let Ok(ref server) = srv {
        server.print_addrs();
    }

    let _ = recv.recv();

    tracing::info!("Terminating...");
    drop(srv);
    tracing::info!("Exit Process.");
    std::process::exit(0);
}
