use clap::Parser;
use ordinary_admin::server::Admin;
use ordinary_app::server::App;
use ordinary_auth::Auth;
use ordinary_config::{AdminLimits, AppConfig, RedactedHashAlg};
use ordinary_storage::saferlmdb::EnvBuilder;
use ordinary_storage::{Storage, saferlmdb};
use qrcodegen::{QrCode, QrCodeEcc};
use rand_chacha::rand_core::{RngCore, SeedableRng};
use std::error::Error;
use std::fmt::Display;
use std::fs::{DirEntry, File};
use std::io::Write;
use std::net::Ipv6Addr;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;

#[derive(Clone)]
pub enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}

impl LogLevel {
    fn to_level_filter(&self) -> LevelFilter {
        match self {
            Self::Error => LevelFilter::ERROR,
            Self::Warn => LevelFilter::WARN,
            Self::Info => LevelFilter::INFO,
            Self::Debug => LevelFilter::DEBUG,
            Self::Trace => LevelFilter::TRACE,
        }
    }
}

impl clap::ValueEnum for LogLevel {
    fn value_variants<'a>() -> &'a [Self] {
        &[
            Self::Error,
            Self::Warn,
            Self::Info,
            Self::Debug,
            Self::Trace,
        ]
    }

    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
        match self {
            Self::Error => Some(clap::builder::PossibleValue::new("error")),
            Self::Warn => Some(clap::builder::PossibleValue::new("warn")),
            Self::Info => Some(clap::builder::PossibleValue::new("info")),
            Self::Debug => Some(clap::builder::PossibleValue::new("debug")),
            Self::Trace => Some(clap::builder::PossibleValue::new("trace")),
        }
    }
}

impl Display for LogLevel {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str = match self {
            Self::Error => String::from("error"),
            Self::Warn => String::from("warn"),
            Self::Info => String::from("info"),
            Self::Debug => String::from("debug"),
            Self::Trace => String::from("trace"),
        };
        write!(f, "{str}")
    }
}

#[derive(Clone)]
pub enum LogFileRotation {
    Day,
    Hour,
    Minute,
    Never,
}

impl clap::ValueEnum for LogFileRotation {
    fn value_variants<'a>() -> &'a [Self] {
        &[Self::Day, Self::Hour, Self::Minute, Self::Never]
    }

    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
        match self {
            Self::Day => Some(clap::builder::PossibleValue::new("day")),
            Self::Hour => Some(clap::builder::PossibleValue::new("hour")),
            Self::Minute => Some(clap::builder::PossibleValue::new("minute")),
            Self::Never => Some(clap::builder::PossibleValue::new("never")),
        }
    }
}

impl Display for LogFileRotation {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str = match self {
            Self::Day => String::from("day"),
            Self::Hour => String::from("hour"),
            Self::Minute => String::from("minute"),
            Self::Never => String::from("never"),
        };
        write!(f, "{str}")
    }
}

#[derive(Clone)]
pub enum ProvisionMode {
    Localhost,
    Staging,
    Production,
}

impl clap::ValueEnum for ProvisionMode {
    fn value_variants<'a>() -> &'a [Self] {
        &[Self::Staging, Self::Production, Self::Localhost]
    }

    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
        match self {
            Self::Localhost => Some(clap::builder::PossibleValue::new("localhost")),
            Self::Staging => Some(clap::builder::PossibleValue::new("staging")),
            Self::Production => Some(clap::builder::PossibleValue::new("production")),
        }
    }
}

impl Display for ProvisionMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let str = match self {
            Self::Localhost => String::from("localhost"),
            Self::Staging => String::from("staging"),
            Self::Production => String::from("production"),
        };
        write!(f, "{str}")
    }
}

#[allow(clippy::struct_excessive_bools)]
#[derive(Parser)]
#[command(version, about, long_about = None)]
#[command(propagate_version = true)]
pub struct Cli {
    /// whether a multi-domain project should be initialized
    #[arg(long, default_value_t = false)]
    pub init: bool,

    /// environment (e.g production, development, staging)
    #[arg(long, default_value_t = String::from("staging"))]
    pub environment: String,

    /// domain name for admin console
    #[arg(long)]
    pub admin_domain: Option<String>,

    /// contacts for the admin domain cert provisioning
    #[arg(long)]
    pub admin_contacts: Option<Vec<String>>,

    /// give each app its own port
    #[arg(long, default_value_t = false)]
    pub dedicated_ports: bool,

    /// use a different domain than the config
    #[arg(long)]
    pub domain_override: Option<String>,

    /// user to run the instance under
    #[arg(long, default_value_t = String::from("root"))]
    pub user: String,

    /// instance user password
    #[arg(long, default_value_t = String::from("password"))]
    pub password: String,

    /// store the MFA key instead of copying a QR code
    #[arg(long, default_value_t = false)]
    pub mfa_stored: bool,

    /// specify the data directory
    #[arg(long, default_value_t = String::from("./.ordinary"))]
    pub data_dir: String,

    /// for running a standalone project. (project must already be built)
    #[arg(long)]
    pub project_dir: Option<String>,

    /// run without HTTPS
    #[arg(long, default_value_t = false)]
    pub insecure: bool,

    /// run with insecure cookies
    #[arg(long, default_value_t = false)]
    pub insecure_cookies: bool,

    /// what mode TLS certs should be provisioned in
    #[arg(long, default_value_t = ProvisionMode::Localhost)]
    pub provision: ProvisionMode,

    /// specify HTTP(s) port for server
    #[arg(long)]
    pub port: Option<u16>,

    /// specify HTTP port for server when
    /// running in secure mode.
    #[arg(long)]
    pub redirect_port: Option<u16>,

    /// Storage size in bytes (rounded up to nearest OS page size).
    #[arg(long)]
    pub storage_size: Option<usize>,

    /// logs events to stdio
    #[arg(long, default_value_t = false)]
    pub stdio_logs: bool,

    /// whether to pretty print stdio logs
    #[arg(long, default_value_t = false)]
    pub stdio_pretty: bool,

    /// whether to print stdio logs as json
    #[arg(long, default_value_t = false)]
    pub stdio_json: bool,

    /// persists JSON formatted log lines to <data-dir>/logs/
    #[arg(long, default_value_t = false)]
    pub json_logs: bool,

    // todo: allow setting log levels for admin, app, storage, templates, actions and integrations
    /// base log level for every component
    #[arg(long, default_value_t = LogLevel::Info)]
    pub log_level: LogLevel,

    /// dictates whether timing information should be logged
    #[arg(long, default_value_t = false)]
    pub with_span_timing: bool,

    /// rate at which new log files are generated
    #[arg(long, default_value_t = LogFileRotation::Minute)]
    pub log_file_rotation: LogFileRotation,

    /// whether HTTP request and response headers are logged
    #[arg(long, default_value_t = false)]
    pub log_headers: bool,

    #[arg(long, default_value_t = String::from("none"))]
    pub redacted_header_hash: String,

    /// whether IP Addresses are logged with HTTP requests
    #[arg(long, default_value_t = false)]
    pub log_ips: bool,

    /// whether storage sizes are logged
    #[arg(long, default_value_t = false)]
    pub log_size: bool,

    /// whether to index logs so they can be queried
    #[arg(long, default_value_t = false)]
    pub log_index: bool,

    /// min duration between log indexing operations. (seconds)
    #[arg(long, default_value_t = 15)]
    pub log_sync_interval_min: u64,

    /// duration between log indexing operations. (seconds)
    #[arg(long, default_value_t = 45)]
    pub log_sync_interval_max: u64,

    /// max period of time logs are indexed/stored
    #[arg(long, default_value_t = 7)]
    pub log_max_ttl_days: u16,
}

fn traverse(dir: &Path, cb: &dyn Fn(&DirEntry)) -> std::io::Result<()> {
    if dir.is_dir() {
        for entry in std::fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir() {
                traverse(&path, cb)?;
            } else {
                cb(&entry);
            }
        }
    }
    Ok(())
}

pub fn setup(cli: &Cli) -> Result<PathBuf, Box<dyn Error>> {
    let logs_dir = match &cli.project_dir {
        Some(project_dir) => {
            let config_str = std::fs::read_to_string(Path::new(project_dir).join("ordinary.json"))?;
            let config: AppConfig = serde_json::from_str(&config_str)?;

            Path::new(&cli.data_dir).join(config.domain).join("logs")
        }
        None => Path::new(&cli.data_dir)
            .join("environments")
            .join(&cli.environment)
            .join("logs"),
    };

    let json_log = if cli.json_logs {
        let appender = match cli.log_file_rotation {
            LogFileRotation::Day => tracing_appender::rolling::daily(logs_dir.join("files"), "log"),
            LogFileRotation::Hour => {
                tracing_appender::rolling::hourly(logs_dir.join("files"), "log")
            }
            LogFileRotation::Minute => {
                tracing_appender::rolling::minutely(logs_dir.join("files"), "log")
            }
            LogFileRotation::Never => {
                tracing_appender::rolling::never(logs_dir.join("files"), "log")
            }
        };

        let json_log = tracing_subscriber::fmt::layer()
            .json()
            .with_writer(appender)
            .with_target(false)
            .with_current_span(false)
            .with_span_events(if cli.with_span_timing {
                FmtSpan::CLOSE
            } else {
                FmtSpan::NONE
            });

        Some(json_log)
    } else {
        None
    };

    let (base, pretty, json) = if cli.stdio_logs {
        if cli.stdio_pretty {
            (
                None,
                Some(tracing_subscriber::fmt::layer().pretty().with_span_events(
                    if cli.with_span_timing {
                        FmtSpan::CLOSE
                    } else {
                        FmtSpan::NONE
                    },
                )),
                None,
            )
        } else if cli.stdio_json {
            (
                None,
                None,
                Some(
                    tracing_subscriber::fmt::layer()
                        .json()
                        .with_target(false)
                        .with_current_span(false)
                        .with_span_events(if cli.with_span_timing {
                            FmtSpan::CLOSE
                        } else {
                            FmtSpan::NONE
                        }),
                ),
            )
        } else {
            (
                Some(
                    tracing_subscriber::fmt::layer()
                        .with_target(false)
                        .with_span_events(if cli.with_span_timing {
                            FmtSpan::CLOSE
                        } else {
                            FmtSpan::NONE
                        }),
                ),
                None,
                None,
            )
        }
    } else {
        (None, None, None)
    };

    let log_level_str = cli.log_level.to_string();

    let directives = [
        ("krystalship", &log_level_str),      // studio
        ("hydrospanner", &log_level_str),     // utils
        ("certifiably", &log_level_str),      // certs
        ("cbwaw", &log_level_str),            // auth
        ("godfather", &log_level_str),        // admin
        ("skyhook", &log_level_str),          // app
        ("stencilr", &log_level_str),         // templates
        ("startmeup", &log_level_str),        // actions
        ("gobetween", &log_level_str),        // integrations
        ("stewball", &log_level_str),         // storage
        ("insightful", &log_level_str),       // storage
        ("tower_http", &log_level_str),       // http
        ("axum::rejection", &"trace".into()), // http
    ];

    let mut directives_string = format!("ordinaryd={}", &log_level_str);

    for (lib, lvl) in directives {
        directives_string = format!("{directives_string},{lib}={lvl}",);
    }

    let filter = tracing_subscriber::EnvFilter::builder()
        .with_default_directive(cli.log_level.to_level_filter().into())
        .parse(directives_string)?;

    tracing_subscriber::registry()
        .with(filter)
        .with(base)
        .with(pretty)
        .with(json)
        .with(json_log)
        .init();

    std::panic::set_hook(Box::new(|info| {
        if let Some(msg) = info.payload_as_str()
            && let Some(loc) = info.location()
        {
            tracing::error!(%loc, msg, "panic");
        } else if let Some(loc) = info.location() {
            tracing::error!(%loc, "panic");
        }
    }));

    Ok(logs_dir)
}

pub async fn run(cli: &Cli, logs_dir: &Path) -> Result<(), Box<dyn Error>> {
    match rustls::crypto::aws_lc_rs::default_provider().install_default() {
        Ok(()) => {}
        Err(_self) => {
            tracing::error!("failed to install default crypto provider for rustls");
        }
    }

    let limits = AdminLimits::default();

    if let Some(project_dir) = &cli.project_dir {
        // standalone
        tracing::debug!("installing standalone project...");

        let project_path = Path::new(&project_dir);

        tracing::debug!("reading config...");
        let config_str = std::fs::read_to_string(project_path.join("ordinary.json"))?;
        let mut config: AppConfig = serde_json::from_str(&config_str)?;

        if let Some(domain_override) = &cli.domain_override {
            config.domain = domain_override.clone();
        }

        let data_dir = Path::new(&cli.data_dir).join(&config.domain);

        tracing::debug!("setting up DB environment...");
        let store_path = data_dir.join("store");
        std::fs::create_dir_all(&store_path)?;

        let ps = page_size::get() as u64;

        // round up to full OS page
        let remainder = config.storage_size % ps;
        let mapsize = (config.storage_size - remainder) + ps;

        tracing::info!(mapsize = %bytesize::ByteSize(mapsize).display().si_short());

        let env = Arc::new(unsafe {
            let mut env_builder = EnvBuilder::new()?;
            env_builder.set_maxreaders(126)?;
            env_builder.set_mapsize(usize::try_from(mapsize)?)?;
            env_builder.set_maxdbs(13)?;
            env_builder.open(
                store_path.to_str().expect("store_path not a str"),
                &saferlmdb::open::Flags::empty(),
                0o600,
            )?
        });

        tracing::debug!("setting up auth...");

        let keys_dir = data_dir.join("keys");
        std::fs::create_dir_all(&keys_dir)?;

        let auth_key_path = keys_dir.join("auth");

        let auth_key: [u8; 32] = if auth_key_path.exists() && auth_key_path.is_file() {
            let auth_key = std::fs::read(&auth_key_path)?;
            let auth_key: [u8; 32] = auth_key[..].try_into()?;
            auth_key
        } else {
            let mut auth_key = [0u8; 32];
            let mut rng = rand_chacha::ChaCha20Rng::from_os_rng();

            rng.fill_bytes(&mut auth_key[..]);

            let mut auth_key_file = File::create(auth_key_path)?;
            auth_key_file.write_all(&auth_key)?;

            auth_key
        };

        tracing::debug!("initializing auth...");

        let auth = Arc::new(Auth::new(
            config.domain.clone(),
            config.auth.clone(),
            auth_key,
            env.clone(),
        )?);

        tracing::debug!("initializing storage...");

        let storage_key_path = keys_dir.join("storage");

        let storage_key: [u8; 32] = if storage_key_path.exists() && storage_key_path.is_file() {
            let storage_key = std::fs::read(&storage_key_path).expect("failed to read storage key");
            let storage_key: [u8; 32] = storage_key[..]
                .try_into()
                .expect("failed to get fixed storage key");
            storage_key
        } else {
            let mut storage_key = [0u8; 32];
            let mut rng = rand_chacha::ChaCha20Rng::from_os_rng();

            rng.fill_bytes(&mut storage_key[..]);

            let mut storage_key_file =
                File::create(storage_key_path).expect("failed to create storage key file");
            storage_key_file
                .write_all(&storage_key)
                .expect("failed to write storage key");

            storage_key
        };

        let storage = Arc::new(Storage::new(
            limits.storage.clone(),
            if let Some(models) = &config.models {
                models.clone()
            } else {
                vec![]
            },
            if let Some(content) = &config.content {
                content.definitions.clone()
            } else {
                vec![]
            },
            storage_key,
            &env,
            store_path.join("search"),
            cli.log_size,
        )?);

        tracing::debug!("instantiating app...");
        let app = Arc::new(App::new(
            config.clone(),
            limits,
            auth,
            storage,
            !cli.insecure_cookies,
            cli.log_headers,
            cli.log_ips,
            None,
            None,
        )?);

        let app_clone = app.clone();

        tracing::debug!("copying content...");
        if let Some(content) = config.content {
            let content_path = Path::new(&content.file_path);
            let objects_json = std::fs::read_to_string(project_path.join(content_path))?;

            let objects: Vec<ordinary_types::ContentObject> = serde_json::from_str(&objects_json)?;

            app.update_content(&objects)?;
        }

        tracing::debug!("copying assets...");
        if let Some(assets) = config.assets {
            let parent_path = &project_path.join(&assets.dir_path);

            traverse(parent_path, &|entry| {
                let path = entry.path();

                if path.ends_with(".DS_Store") {
                    tracing::warn!("ignoring .DS_Store");
                } else if let Ok(content) = std::fs::read(&path)
                    && let Ok(path) = path.strip_prefix(parent_path)
                    && let Some(path) = path.to_str()
                    && let Err(err) = app_clone.put_asset(path, &content)
                {
                    tracing::error!(%err);
                }
            })?;
        }

        if config.auth.is_some() || config.client_tracing == Some(true) {
            let core_js =
                std::fs::read_to_string(project_path.join("./.ordinary/gen/client/js/core.js"))?;

            app.put_asset(
                &format!("{}/js/core.js", config.version),
                core_js.replace("{{ version }}", &config.version).as_bytes(),
            )?;
        }

        if config.auth.is_some() {
            let js_client_js =
                std::fs::read_to_string(project_path.join("./.ordinary/gen/client/js/client.js"))?;

            app.put_asset(
                &format!("{}/js/client.js", config.version),
                js_client_js
                    .replace("{{ version }}", &config.version)
                    .as_bytes(),
            )?;
        }

        if config.auth.is_some()
            || config.client_e2ee == Some(true)
            || config.client_rendering == Some(true)
        {
            let client_js = std::fs::read_to_string(
                project_path.join("./.ordinary/gen/client/wasm/client.js"),
            )?;
            let client_wasm =
                std::fs::read(project_path.join("./.ordinary/gen/client/wasm/client_bg_opt.wasm"))?;

            app.put_asset(
                &format!("{}/wasm/client.js", config.version),
                client_js
                    .replace("{{ version }}", &config.version)
                    .as_bytes(),
            )?;
            app.put_asset(
                &format!("{}/wasm/client_bg.wasm", config.version),
                &client_wasm,
            )?;
        }

        if let Some(actions) = config.actions {
            if !actions.is_empty() {
                tracing::debug!("installing actions...")
            }

            for config in actions {
                let path = project_path
                    .join(&config.dir_path)
                    .join("target/wasm32-wasip1/release/action.wasm");
                let action_bytes = std::fs::read(path)?;

                app.set_action(config.idx, action_bytes.as_slice())?;
            }
        }

        if let Some(templates) = config.templates {
            if !templates.is_empty() {
                tracing::debug!("installing templates...")
            }

            for config in templates {
                let path = project_path
                    .join(".ordinary")
                    .join("gen")
                    .join("server")
                    .join(&config.name)
                    .join("target/wasm32-wasip1/release/template.wasm");
                let template_bytes = std::fs::read(path)?;

                app.set_template(config.idx, template_bytes.as_slice())?;
            }
        }

        let port = if cli.insecure {
            cli.port.unwrap_or(config.port.unwrap_or(80))
        } else {
            cli.port.unwrap_or(config.port.unwrap_or(443))
        };

        let redirect_listener = if cli.insecure {
            None
        } else {
            let redirect_port = cli
                .redirect_port
                .unwrap_or(config.redirect_port.unwrap_or(80));

            Some(TcpListener::bind((Ipv6Addr::UNSPECIFIED, redirect_port)).await?)
        };

        let listener = TcpListener::bind((Ipv6Addr::UNSPECIFIED, port)).await?;

        app.start(
            listener,
            redirect_listener,
            if cli.insecure {
                ordinary_app::server::SecurityMode::Insecure
            } else {
                ordinary_app::server::SecurityMode::Secure(
                    Path::new(&cli.data_dir).join(&config.domain).join("certs"),
                    match cli.provision {
                        ProvisionMode::Localhost => ordinary_app::server::ProvisionMode::Localhost,
                        ProvisionMode::Staging => ordinary_app::server::ProvisionMode::Staging,
                        ProvisionMode::Production => {
                            ordinary_app::server::ProvisionMode::Production
                        }
                    },
                )
            },
            None,
        )
        .await?;
    } else {
        // multi-domain
        tracing::debug!("starting multi-domain...");

        let environment_path = Path::new(&cli.data_dir)
            .join("environments")
            .join(&cli.environment);

        std::fs::create_dir_all(&environment_path)?;

        let server_span =
            tracing::info_span!("server", env = &cli.environment, pid = std::process::id());

        let Some(admin_domain) = &cli.admin_domain else {
            return Err(
                "`--admin-domain` not supplied; must be included for multi-domain environments."
                    .into(),
            );
        };

        let Some(storage_size) = cli.storage_size else {
            return Err(
                "`--storage-size` not supplied; must be included for multi-domain environments."
                    .into(),
            );
        };

        let log_manager = Arc::new(ordinary_monitor::Manager::new(
            logs_dir.into(),
            admin_domain.clone(),
            (cli.log_sync_interval_min, cli.log_sync_interval_max),
            cli.log_max_ttl_days,
            Some(server_span.clone()),
            cli.log_size,
        )?);

        if cli.init {
            tracing::debug!("initializing admin...");

            match Admin::init(
                &cli.environment,
                admin_domain,
                &cli.password,
                &cli.data_dir,
                storage_size,
                log_manager.clone(),
            ) {
                Ok((mfa_url, secret)) => {
                    if cli.mfa_stored {
                        let mut mfa_secret_file =
                            File::create(environment_path.join("mfa_secret"))?;
                        mfa_secret_file.write_all(&secret)?;
                    } else {
                        // taken from QR code demo on qrcodegen repo https://github.com/nayuki/QR-Code-generator/blob/2c9044de6b049ca25cb3cd1649ed7e27aa055138/rust/examples/qrcodegen-demo.rs#L196
                        let qr: QrCode = QrCode::encode_text(&mfa_url, QrCodeEcc::Medium)?;
                        let border: i32 = 4;
                        for y in -border..qr.size() + border {
                            for x in -border..qr.size() + border {
                                let c: char = if qr.get_module(x, y) { '█' } else { ' ' };
                                print!("{c}{c}");
                            }
                            println!();
                        }
                        println!("{mfa_url}");
                    }
                }
                Err(err) => tracing::error!(err),
            }
        }

        tracing::debug!("constructing admin...");

        let admin = Admin::new(
            &cli.environment,
            admin_domain,
            &cli.data_dir,
            storage_size,
            log_manager.clone(),
        )?;
        let cert_dir_path = environment_path.join("certs");

        let port = if cli.insecure {
            cli.port.unwrap_or(80)
        } else {
            cli.port.unwrap_or(443)
        };

        let listener = TcpListener::bind((Ipv6Addr::UNSPECIFIED, port)).await?;

        let redirect_listener = if cli.insecure {
            None
        } else {
            let redirect_port = cli.redirect_port.unwrap_or(80);

            Some(TcpListener::bind((Ipv6Addr::UNSPECIFIED, redirect_port)).await?)
        };

        if cli.log_index
            && let Err(err) = log_manager.start()
        {
            tracing::error!(%err);
        }

        tracing::debug!("starting admin...");

        let redacted_hash = match cli.redacted_header_hash.as_str() {
            "blake2" => Some(RedactedHashAlg::Blake2),
            "blake3" => Some(RedactedHashAlg::Blake3),
            _ => None,
        };

        admin
            .start(
                server_span,
                if cli.insecure {
                    ordinary_admin::server::SecurityMode::Insecure
                } else {
                    ordinary_admin::server::SecurityMode::Secure(
                        cert_dir_path,
                        match cli.provision {
                            ProvisionMode::Localhost => {
                                ordinary_admin::server::ProvisionMode::Localhost
                            }
                            ProvisionMode::Staging => {
                                ordinary_admin::server::ProvisionMode::Staging
                            }
                            ProvisionMode::Production => {
                                ordinary_admin::server::ProvisionMode::Production
                            }
                        },
                    )
                },
                listener,
                !cli.insecure_cookies,
                cli.log_headers,
                cli.log_ips,
                cli.log_size,
                admin_domain.clone(),
                cli.admin_contacts.clone().unwrap_or(vec![]),
                redirect_listener,
                cli.dedicated_ports,
                cli.log_index,
                redacted_hash,
            )
            .await?;
    }

    Ok(())
}
