// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

pub mod types;

mod cli;

use crate::cli::*;
use crate::types::*;
use clap::Parser;
use p2p_broker::interfaces::*;
use p2p_broker::server_ws::run_server_v0;
use p2p_broker::types::*;
use p2p_broker::utils::*;
use p2p_net::types::*;
use p2p_net::utils::is_private_ip;
use p2p_net::utils::is_public_ip;
use p2p_net::utils::is_public_ipv4;
use p2p_net::utils::is_public_ipv6;
use p2p_net::utils::{
    gen_dh_keys, is_ipv4_global, is_ipv4_private, is_ipv6_global, is_ipv6_private,
};
use p2p_net::{WS_PORT, WS_PORT_REVERSE_PROXY};
use p2p_repo::log::*;
use p2p_repo::types::SymKey;
use p2p_repo::utils::ed_keypair_from_priv_bytes;
use p2p_repo::{
    types::{PrivKey, PubKey},
    utils::{decode_key, generate_keypair, sign, verify},
};
use serde_json::{from_str, to_string_pretty};
use std::fs::{read_to_string, write};
use std::io::ErrorKind;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::path::PathBuf;
use zeroize::Zeroize;

use addr::parser::DnsName;
use addr::psl::List;
use lazy_static::lazy_static;
use regex::Regex;

//For windows: {846EE342-7039-11DE-9D20-806E6F6E6963}
//For the other OSes: en0 lo ...
#[cfg(not(target_os = "windows"))]
lazy_static! {
    static ref RE_INTERFACE: Regex = Regex::new(r"^([0-9a-z]{2,16})(\:\d{1,5})?$").unwrap();
}
#[cfg(target_os = "windows")]
lazy_static! {
    static ref RE_INTERFACE: Regex = Regex::new(
        r"^(\{[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\})(\:\d{1,5})?$"
    )
    .unwrap();
}

lazy_static! {
    static ref RE_IPV6_WITH_PORT: Regex =
        Regex::new(r"^\[([0-9a-fA-F:]{3,39})\](\:\d{1,5})?$").unwrap();
}

pub static DEFAULT_PORT: u16 = WS_PORT;

pub static DEFAULT_TLS_PORT: u16 = 443;

fn parse_interface_and_port_for(
    string: &String,
    for_option: &str,
    default_port: u16,
) -> Result<(String, u16), ()> {
    let c = RE_INTERFACE.captures(string);

    if c.is_some() && c.as_ref().unwrap().get(1).is_some() {
        let cap = c.unwrap();
        let interface = cap.get(1).unwrap().as_str();
        let port = match cap.get(2) {
            None => default_port,
            Some(p) => {
                let mut chars = p.as_str().chars();
                chars.next();
                match from_str::<u16>(chars.as_str()) {
                    Err(_) => default_port,
                    Ok(p) => {
                        if p == 0 {
                            default_port
                        } else {
                            p
                        }
                    }
                }
            }
        };
        Ok((interface.to_string(), port))
    } else {
        log_err!(
            "The <INTERFACE:PORT> value submitted for the {} option is invalid. It should be the name of an interface found with --list-interfaces, with an optional port suffix of the form :123. cannot start",
            for_option
        );
        Err(())
    }
}

fn parse_ipv6_for(string: String, for_option: &str) -> Result<Ipv6Addr, ()> {
    string.parse::<Ipv6Addr>().map_err(|_| {
        log_err!(
            "The <IPv6> value submitted for the {} option is invalid. cannot start",
            for_option
        )
    })
}

fn parse_ipv4_and_port_for(
    string: String,
    for_option: &str,
    default_port: u16,
) -> Result<(Ipv4Addr, u16), ()> {
    let parts: Vec<&str> = string.split(":").collect();
    let ipv4 = parts[0].parse::<Ipv4Addr>().map_err(|_| {
        log_err!(
            "The <IPv4:PORT> value submitted for the {} option is invalid. cannot start",
            for_option
        )
    })?;

    let port;
    if parts.len() > 1 {
        port = match from_str::<u16>(parts[1]) {
            Err(_) => default_port,
            Ok(p) => {
                if p == 0 {
                    default_port
                } else {
                    p
                }
            }
        };
    } else {
        port = default_port;
    }
    return Ok((ipv4, port));
}

fn parse_ip_and_port_for(string: String, for_option: &str) -> Result<(IpAddr, u16), ()> {
    let c = RE_IPV6_WITH_PORT.captures(&string);
    let ipv6;
    let port;
    if c.is_some() && c.as_ref().unwrap().get(1).is_some() {
        let cap = c.unwrap();
        let ipv6_str = cap.get(1).unwrap().as_str();
        port = match cap.get(2) {
            None => DEFAULT_PORT,
            Some(p) => {
                let mut chars = p.as_str().chars();
                chars.next();
                match from_str::<u16>(chars.as_str()) {
                    Err(_) => DEFAULT_PORT,
                    Ok(p) => {
                        if p == 0 {
                            DEFAULT_PORT
                        } else {
                            p
                        }
                    }
                }
            }
        };
        let ipv6 = ipv6_str.parse::<Ipv6Addr>().map_err(|_| {
            log_err!(
                "The <[IPv6]:PORT> value submitted for the {} option is invalid. cannot start",
                for_option
            )
        })?;
        return Ok((IpAddr::V6(ipv6), port));
    } else {
        // we try just an IPV6 without port
        let ipv6_res = string.parse::<Ipv6Addr>();
        if ipv6_res.is_err() {
            // let's try IPv4

            return parse_ipv4_and_port_for(string, for_option, DEFAULT_PORT)
                .map(|ipv4| (IpAddr::V4(ipv4.0), ipv4.1));
        } else {
            ipv6 = ipv6_res.unwrap();
            port = DEFAULT_PORT;
            return Ok((IpAddr::V6(ipv6), port));
        }
    }
}

fn parse_triple_interface_and_port_for(
    string: &String,
    for_option: &str,
) -> Result<((String, u16), (Option<Ipv6Addr>, (Ipv4Addr, u16))), ()> {
    let parts: Vec<&str> = string.split(',').collect();
    if parts.len() < 2 {
        log_err!(
            "The <PRIVATE_INTERFACE:PORT,[PUBLIC_IPV6,]PUBLIC_IPV4:PORT> value submitted for the {} option is invalid. It should be composed of at least 2 parts separated by a comma. cannot start",
            for_option
        );
        return Err(());
    }
    let first_part = parse_interface_and_port_for(
        &parts[0].to_string(),
        &format!("private interface+PORT (left) part of the {}", for_option),
        DEFAULT_PORT,
    );
    if first_part.is_err() {
        return Err(());
    }

    let mut middle_part = None;
    if parts.len() == 3 {
        let middle_part_res = parse_ipv6_for(
            parts[1].to_string(),
            &format!("public IPv6 (middle) part of the {}", for_option),
        );
        if middle_part_res.is_err() {
            return Err(());
        }
        middle_part = middle_part_res.ok();
    }

    let last_part = parse_ipv4_and_port_for(
        parts[parts.len() - 1].to_string(),
        &format!("public IPv4+PORT (right) part of the {}", for_option),
        DEFAULT_PORT,
    );
    if last_part.is_err() {
        return Err(());
    }

    Ok((first_part.unwrap(), (middle_part, last_part.unwrap())))
}

fn parse_domain_and_port(
    domain_string: &String,
    option: &str,
    default_port: u16,
) -> Result<(String, String, u16), ()> {
    let parts: Vec<&str> = domain_string.split(':').collect();

    // check validity of domain name
    let valid_domain = List.parse_dns_name(parts[0]);
    match valid_domain {
        Err(e) => {
            log_err!(
                "The domain name provided for option {} is invalid. {}. cannot start",
                option,
                e.to_string()
            );
            return Err(());
        }
        Ok(name) => {
            if !name.has_known_suffix() {
                log_err!(
                            "The domain name provided for option {} is invalid. Unknown suffix in public list. cannot start", option
                        );
                return Err(());
            }
        }
    }

    let port = if parts.len() > 1 {
        match from_str::<u16>(parts[1]) {
            Err(_) => default_port,
            Ok(p) => {
                if p == 0 {
                    default_port
                } else {
                    p
                }
            }
        }
    } else {
        default_port
    };
    let mut domain_with_port = parts[0].clone().to_string();
    if port != default_port {
        domain_with_port.push_str(&format!(":{}", port));
    }
    Ok((parts[0].to_string(), domain_with_port, port))
}

fn prepare_accept_forward_for_domain(
    domain: String,
    args: &mut Cli,
) -> Result<AcceptForwardForV0, ()> {
    if args.domain_peer.is_some() {
        let key = decode_key(args.domain_peer.as_ref().unwrap().as_str())?;
        args.domain_peer.as_mut().unwrap().zeroize();

        Ok(AcceptForwardForV0::PublicDomainPeer((
            domain,
            PrivKey::Ed25519PrivKey(key),
            "".to_string(),
        )))
    } else {
        Ok(AcceptForwardForV0::PublicDomain((domain, "".to_string())))
    }
}

#[async_std::main]
async fn main() -> std::io::Result<()> {
    main_inner()
        .await
        .map_err(|_| ErrorKind::InvalidInput.into())
}

async fn main_inner() -> Result<(), ()> {
    let mut args = Cli::parse();

    if args.list_interfaces {
        println!("list of network interfaces");
        print_interfaces();
        return Ok(());
    }

    if std::env::var("RUST_LOG").is_err() {
        if args.verbose == 0 {
            std::env::set_var("RUST_LOG", "warn");
        } else if args.verbose == 1 {
            std::env::set_var("RUST_LOG", "info");
        } else if args.verbose == 2 {
            std::env::set_var("RUST_LOG", "debug");
        } else if args.verbose >= 3 {
            std::env::set_var("RUST_LOG", "trace");
        }
    }
    env_logger::init();

    log_info!("Starting NextGraph daemon (ngd)");

    log_debug!("base {:?}", args.base);

    let mut path = PathBuf::from(&args.base);
    path.push("server");
    if !path.is_absolute() {
        path = std::env::current_dir().unwrap().join(path);
    }

    log_debug!("cur {}", std::env::current_dir().unwrap().display());
    log_debug!("home {}", path.to_str().unwrap());
    std::fs::create_dir_all(path.clone()).unwrap();

    // reading key from file, if any
    let mut key_path = path.clone();
    key_path.push("key");
    let key_from_file: Option<[u8; 32]>;
    let res = |key_path| -> Result<[u8; 32], &str> {
        let mut file = read_to_string(key_path).map_err(|_| "")?;
        let first_line = file.lines().nth(0).ok_or("empty file")?;
        let res = decode_key(first_line.trim()).map_err(|_| "invalid file");
        file.zeroize();
        res
    }(&key_path);

    if res.is_err() && res.unwrap_err().len() > 0 {
        log_err!(
            "provided key file is incorrect. {}. cannot start",
            res.unwrap_err()
        );
        return Err(());
    }
    key_from_file = res.ok();

    let mut keys: [[u8; 32]; 4] = match &args.key {
        Some(key_string) => {
            if key_from_file.is_some() {
                log_err!("provided --key option will not be used as a key file is already present");
                args.key.as_mut().unwrap().zeroize();
                gen_broker_keys(key_from_file)
            } else {
                let res = decode_key(key_string.as_str())
                    .map_err(|_| log_err!("provided key is invalid. cannot start"))?;
                if args.save_key {
                    let mut master_key = base64_url::encode(&res);
                    write(key_path.clone(), &master_key).map_err(|e| {
                        log_err!("cannot save key to file. {}.cannot start", e.to_string())
                    })?;
                    master_key.zeroize();
                    log_info!("The key has been saved to {}", key_path.to_str().unwrap());
                }
                args.key.as_mut().unwrap().zeroize();
                gen_broker_keys(Some(res))
            }
        }
        None => {
            if key_from_file.is_some() {
                gen_broker_keys(key_from_file)
            } else {
                let res = gen_broker_keys(None);
                let mut master_key = base64_url::encode(&res[0]);
                if args.save_key {
                    write(key_path.clone(), &master_key).map_err(|e| {
                        log_err!("cannot save key to file. {}.cannot start", e.to_string())
                    })?;
                    log_info!("The key has been saved to {}", key_path.to_str().unwrap());
                } else {
                    // on purpose we don't log the key, just print it out to stdout, as it should not be saved in logger's files
                    println!("YOUR GENERATED KEY IS: {}", master_key);
                    log_err!("At your request, the key wasn't saved. If you want to save it to disk, use ---save-key");
                    log_err!("provide it again to the next start of ngd with --key option or NG_SERVER_KEY env variable");
                }
                master_key.zeroize();
                res
            }
        }
    };

    key_from_file.and_then(|mut key| {
        key.zeroize();
        None::<()>
    });

    // DEALING WITH CONFIG

    // reading config from file, if any
    let mut config_path = path.clone();
    config_path.push("config.json");
    let mut config: Option<DaemonConfig>;
    let res = |config_path| -> Result<DaemonConfig, String> {
        let file = read_to_string(config_path).map_err(|_| "".to_string())?;
        from_str(&file).map_err(|e| e.to_string())
    }(&config_path);

    if res.is_err() && res.as_ref().unwrap_err().len() > 0 {
        log_err!(
            "provided config file is incorrect. {}. cannot start",
            res.unwrap_err()
        );
        return Err(());
    }
    config = res.ok();

    if config.is_some() && args.save_config {
        log_err!("A config file is present. We cannot override it with Quick config options. cannot start");
        return Err(());
    }

    if args.local.is_some()
        || args.forward.is_some()
        || args.core.is_some()
        || args.private.is_some()
        || args.public.is_some()
        || args.dynamic.is_some()
        || args.domain.is_some()
        || args.domain_private.is_some()
    {
        // QUICK CONFIG

        if config.is_some() && !args.print_config {
            log_err!(
                "A config file is present. You cannot use the Quick config options on the command-line. In order to use them, delete your config file first. cannot start"
            );
            return Err(());
        }

        if args.domain_peer.is_some() && args.domain_private.is_none() && args.domain.is_none() {
            log_err!(
                "The --domain-peer option can only be set when the --domain or --domain-private option is also present on the command line. cannot start"
            );
            return Err(());
        }

        let mut listeners: Vec<ListenerV0> = vec![];
        let mut overlays_config: BrokerOverlayConfigV0 = BrokerOverlayConfigV0::new();

        let interfaces = get_interface();

        //// --domain

        if args.domain.is_some() {
            let domain_string = args.domain.as_ref().unwrap();
            let parts: Vec<&str> = domain_string.split(',').collect();
            let local_port;
            let (_, domain, _) = if parts.len() == 1 {
                local_port = WS_PORT_REVERSE_PROXY;
                parse_domain_and_port(domain_string, "--domain", DEFAULT_TLS_PORT)?
            } else {
                local_port = match from_str::<u16>(parts[1]) {
                    Err(_) => WS_PORT_REVERSE_PROXY,
                    Ok(p) => {
                        if p == 0 {
                            WS_PORT_REVERSE_PROXY
                        } else {
                            p
                        }
                    }
                };
                parse_domain_and_port(&parts[0].to_string(), "--domain", DEFAULT_TLS_PORT)?
            };

            match find_first(&interfaces, InterfaceType::Loopback) {
                None => {
                    log_err!(
                        "That's pretty unusual, but no loopback interface could be found on your host. --domain option failed for that reason. cannot start"
                    );
                    return Err(());
                }
                Some(loopback) => {
                    overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
                    let mut listener = ListenerV0::new_direct(loopback, !args.no_ipv6, local_port);
                    listener.accept_direct = false;
                    let res = prepare_accept_forward_for_domain(domain, &mut args).map_err(|_| {
                        log_err!("The --domain-peer option has an invalid key. it must be a base64_url encoded serialization of a PrivKey. cannot start")
                    })?;
                    listener.accept_forward_for = res;
                    listeners.push(listener);
                }
            }
        }

        //// --local

        if args.local.is_some() {
            match find_first(&interfaces, InterfaceType::Loopback) {
                None => {
                    log_err!(
                        "That's pretty unusual, but no loopback interface could be found on your host. cannot start"
                    );
                    return Err(());
                }
                Some(loopback) => {
                    overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;

                    if listeners.last().is_some()
                        && listeners.last().unwrap().interface_name == loopback.name
                        && listeners.last().unwrap().port == args.local.unwrap()
                    {
                        if args.domain_peer.is_some() {
                            log_err!(
                                "--local is not allowed if --domain-peer is selected, and they both use the same port. change the port of one of them. cannot start"
                            );
                            return Err(());
                        }
                        let r = listeners.last_mut().unwrap();
                        r.accept_direct = true;
                        r.ipv6 = !args.no_ipv6;
                    } else {
                        listeners.push(ListenerV0::new_direct(
                            loopback,
                            !args.no_ipv6,
                            args.local.unwrap(),
                        ));
                    }
                }
            }
        }

        //// --core

        if args.core.is_some() {
            let arg_value =
                parse_interface_and_port_for(args.core.as_ref().unwrap(), "--core", DEFAULT_PORT)?;

            let if_name = &arg_value.0;
            match find_first_or_name(&interfaces, InterfaceType::Public, &if_name) {
                None => {
                    log_err!(
                        "{}",
                        if if_name == "default" {
                            "We could not find a public IP interface on your host. If you are setting up a server behind a reverse proxy, enter the config manually in the config file. cannot start".to_string()
                        } else {
                            format!(
                                "We could not find a public IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
                                if_name
                            )
                        }
                    );
                    return Err(());
                }
                Some(public) => {
                    overlays_config.core = BrokerOverlayPermission::AllRegisteredUser;
                    let mut listener = ListenerV0::new_direct(public, !args.no_ipv6, arg_value.1);
                    if args.core_with_clients {
                        overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
                    } else {
                        listener.refuse_clients = true;
                    }
                    listener.serve_app = false;
                    listeners.push(listener);
                }
            }
        }

        //// --public

        if args.public.is_some() {
            let arg_value =
                parse_triple_interface_and_port_for(args.public.as_ref().unwrap(), "--public")?;

            let public_part = &arg_value.1;
            let private_part = &arg_value.0;
            let private_interface;
            let if_name = &private_part.0;
            match find_first_or_name(&interfaces, InterfaceType::Private, &if_name) {
                None => {
                    log_err!(
                        "{}",
                        if if_name == "default" {
                            "We could not find a private IP interface on your host for --public option. cannot start"
                                .to_string()
                        } else {
                            format!(
                                "We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
                                if_name
                            )
                        }
                    );
                    return Err(());
                }
                Some(inter) => {
                    private_interface = inter;
                }
            }

            if !is_public_ipv4(&public_part.1 .0)
                || public_part.0.is_some() && !is_public_ipv6(public_part.0.as_ref().unwrap())
            {
                log_err!("The provided IPs are not public. cannot start");
                return Err(());
            }

            if args.no_ipv6 && public_part.0.is_some() {
                log_err!(
                    "The public IP is IPv6 but you selected the --no-ipv6 option. cannot start"
                );
                return Err(());
            }

            overlays_config.core = BrokerOverlayPermission::AllRegisteredUser;
            if !args.public_without_clients {
                overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
            }

            let ipv6 = public_part.0.map(|ipv6| BindAddress {
                port: public_part.1 .1,
                ip: (&IpAddr::V6(ipv6)).into(),
            });

            listeners.push(ListenerV0 {
                interface_name: private_interface.name,
                if_type: private_interface.if_type,
                ipv6: public_part.0.is_some(),
                interface_refresh: 0,
                port: private_part.1,
                discoverable: false,
                refuse_clients: args.public_without_clients,
                serve_app: false,
                accept_direct: false,
                bind_public_ipv6: ipv6.is_some() && args.bind_public_ipv6,
                accept_forward_for: AcceptForwardForV0::PublicStatic((
                    BindAddress {
                        port: public_part.1 .1,
                        ip: (&IpAddr::V4(public_part.1 .0)).into(),
                    },
                    ipv6,
                    "".to_string(),
                )),
            });
        }

        //// --dynamic

        if args.dynamic.is_some() {
            let dynamic_string = args.dynamic.as_ref().unwrap();
            let parts: Vec<&str> = dynamic_string.split(',').collect();

            let arg_value =
                parse_interface_and_port_for(&parts[0].to_string(), "--dynamic", DEFAULT_PORT)?;

            let public_port = if parts.len() == 2 {
                match from_str::<u16>(parts[1]) {
                    Err(_) => DEFAULT_PORT,
                    Ok(p) => {
                        if p == 0 {
                            DEFAULT_PORT
                        } else {
                            p
                        }
                    }
                }
            } else {
                DEFAULT_PORT
            };

            let if_name = &arg_value.0;

            match find_first_or_name(&interfaces, InterfaceType::Private, if_name) {
                None => {
                    log_err!(
                        "{}",
                        if if_name == "default" {
                            "We could not find a private IP interface on your host for --dynamic option. cannot start"
                                .to_string()
                        } else {
                            format!(
                                "We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
                                if_name
                            )
                        }
                    );
                    return Err(());
                }
                Some(inter) => {
                    overlays_config.core = BrokerOverlayPermission::AllRegisteredUser;
                    if !args.public_without_clients {
                        overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
                    }

                    if listeners.last().is_some()
                        && listeners.last().unwrap().interface_name == inter.name
                        && listeners.last().unwrap().port == arg_value.1
                    {
                        let r = listeners.last_mut().unwrap();
                        if r.accept_forward_for != AcceptForwardForV0::No {
                            log_err!("The same private interface is already forwarding with a different setting, probably because of a --public option conflicting with a --dynamic option. Changing the port on one of the interfaces can help. cannot start");
                            return Err(());
                        }
                        panic!("this should never happen. --dynamic created after a --private");
                        //r.ipv6 = !args.no_ipv6;
                        //r.accept_forward_for = AcceptForwardForV0::PublicDyn((public_port, 60, "".to_string()));
                    } else {
                        let mut listener =
                            ListenerV0::new_direct(inter, !args.no_ipv6, arg_value.1);
                        listener.accept_direct = false;
                        listener.refuse_clients = args.public_without_clients;
                        listener.serve_app = false;
                        listener.accept_forward_for =
                            AcceptForwardForV0::PublicDyn((public_port, 60, "".to_string()));
                        listeners.push(listener);
                    }
                }
            }
        }

        //// --domain-private

        if args.domain_private.is_some() {
            let domain_string = args.domain_private.as_ref().unwrap();
            let parts: Vec<&str> = domain_string.split(',').collect();

            let (_, domain, _) =
                parse_domain_and_port(&parts[0].to_string(), "--domain-private", DEFAULT_TLS_PORT)?;

            let bind_string = if parts.len() > 1 { parts[1] } else { "default" };

            let arg_value = parse_interface_and_port_for(
                &bind_string.to_string(),
                "--domain-private",
                WS_PORT_REVERSE_PROXY,
            )?;

            let if_name = &arg_value.0;
            match find_first_or_name(&interfaces, InterfaceType::Private, &if_name) {
                None => {
                    log_err!(
                        "{}",
                        if if_name == "default" {
                            "We could not find a private IP interface on your host for --domain-private option. cannot start"
                                .to_string()
                        } else {
                            format!(
                                "We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
                                if_name
                            )
                        }
                    );
                    return Err(());
                }
                Some(inter) => {
                    overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;

                    let res = prepare_accept_forward_for_domain(domain, &mut args).map_err(|_| {
                        log_err!("The --domain-peer option has an invalid key. it must be a base64_url encoded serialization of a PrivKey. cannot start")})?;

                    if listeners.last().is_some()
                        && listeners.last().unwrap().interface_name == inter.name
                        && listeners.last().unwrap().port == arg_value.1
                    {
                        let r = listeners.last_mut().unwrap();
                        if r.accept_forward_for != AcceptForwardForV0::No {
                            log_err!("The same private interface is already forwarding with a different setting, probably because of a --public or --dynamic option conflicting with the --domain-private option. Changing the port on one of the interfaces can help. cannot start");
                            return Err(());
                        }
                        panic!(
                            "this should never happen. --domain-private created after a --private"
                        );
                        //r.ipv6 = !args.no_ipv6;
                        //r.accept_forward_for = res;
                    } else {
                        let mut listener =
                            ListenerV0::new_direct(inter, !args.no_ipv6, arg_value.1);
                        listener.accept_direct = false;
                        listener.accept_forward_for = res;

                        listeners.push(listener);
                    }
                }
            }
        }

        //// --private

        if args.private.is_some() {
            let arg_value = parse_interface_and_port_for(
                args.private.as_ref().unwrap(),
                "--private",
                DEFAULT_PORT,
            )?;

            let if_name = &arg_value.0;
            match find_first_or_name(&interfaces, InterfaceType::Private, &if_name) {
                None => {
                    log_err!(
                        "{}",
                        if if_name == "default" {
                            "We could not find a private IP interface on your host. cannot start"
                                .to_string()
                        } else {
                            format!(
                                        "We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
                                        if_name
                                    )
                        }
                    );
                    return Err(());
                }
                Some(inter) => {
                    overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;

                    if listeners.last().is_some()
                        && listeners.last().unwrap().interface_name == inter.name
                        && listeners.last().unwrap().port == arg_value.1
                    {
                        if args.domain_peer.is_some() {
                            log_err!(
                                "--private is not allowed if --domain-peer is selected, and they both use the same port. change the port of one of them. cannot start"
                            );
                            return Err(());
                        }
                        let r = listeners.last_mut().unwrap();
                        r.accept_direct = true;
                        r.serve_app = true;
                        r.ipv6 = !args.no_ipv6;
                    } else {
                        listeners.push(ListenerV0::new_direct(inter, !args.no_ipv6, arg_value.1));
                    }
                }
            }
        }

        //// --forward

        if args.forward.is_some() {
            //"[DOMAIN/IP:PORT]@PEERID"
            let forward_string = args.forward.as_ref().unwrap();
            let parts: Vec<&str> = forward_string.split('@').collect();

            if parts.len() != 2 {
                log_err!(
                    "The option --forward is invalid. It must contain two parts separated by a @ character. cannot start"
                );
                return Err(());
            }
            let pub_key_array = decode_key(parts[1])
                .map_err(|_| log_err!("The PEER_ID provided in the --forward option is invalid"))?;
            let peer_id = PubKey::Ed25519PubKey(pub_key_array);

            let server_type = if parts[0].len() > 0 {
                let first_char = parts[0].chars().next().unwrap();

                if first_char == '[' || first_char.is_numeric() {
                    // an IPv6 or IPv4
                    let bind = parse_ip_and_port_for(parts[0].to_string(), "--forward")?;
                    let bind_addr = BindAddress {
                        ip: (&bind.0).into(),
                        port: bind.1,
                    };
                    if is_private_ip(&bind.0) {
                        BrokerServerTypeV0::BoxPrivate(vec![bind_addr])
                    } else if is_public_ip(&bind.0) {
                        BrokerServerTypeV0::BoxPublic(vec![bind_addr])
                    } else {
                        log_err!("Invalid IP address given for --forward option. cannot start");
                        return Err(());
                    }
                } else {
                    // a domain name
                    let (_, domain, _) = parse_domain_and_port(
                        &parts[0].to_string(),
                        "--forward",
                        DEFAULT_TLS_PORT,
                    )?;
                    BrokerServerTypeV0::Domain(domain)
                }
            } else {
                BrokerServerTypeV0::BoxPublicDyn(vec![])
            };
            overlays_config.forward = vec![BrokerServerV0 {
                server_type,
                peer_id,
            }];
        }

        let registration = if args.registration_off {
            RegistrationConfig::Closed
        } else if args.registration_open {
            RegistrationConfig::Open
        } else {
            RegistrationConfig::Invitation
        };

        let admin_user = if args.admin.is_some() {
            args.admin
                .unwrap()
                .as_str()
                .try_into()
                .map_err(|e| {
                    log_warn!("The admin UserId supplied is invalid. no admin user configured.");
                })
                .ok()
        } else {
            None
        };

        config = Some(DaemonConfig::V0(DaemonConfigV0 {
            listeners,
            overlays_configs: vec![overlays_config],
            registration,
            admin_user,
            registration_url: args.registration_url,
        }));

        if args.print_config {
            let json_string = to_string_pretty(config.as_ref().unwrap()).unwrap();
            println!("The Quick config would be:\n{}", json_string);
            return Ok(());
        }

        if args.save_config {
            // saves the config to file
            let json_string = to_string_pretty(config.as_ref().unwrap()).unwrap();
            write(config_path.clone(), json_string).map_err(|e| {
                log_err!(
                    "cannot save config to file. {}. cannot start",
                    e.to_string()
                )
            })?;
            log_info!(
                "The config file has been saved to {}",
                config_path.to_str().unwrap()
            );
            log_info!(
                "You not be able to use any Quick config options anymore on the command line at the next command-line start of the server. But you can go to modify the config file directly, or delete it.",
            );
        }
    } else {
        if config.is_none() {
            log_err!(
                "No Quick config option passed, neither is a config file present. We cannot start the server. Choose at least one Quick config option. see --help for details"
            );
            return Err(());
        }
        if args.print_config {
            let json_string = to_string_pretty(config.as_ref().unwrap()).unwrap();
            println!("The saved config is:\n{}", json_string);
            return Ok(());
        }
    }

    let (privkey, pubkey) = ed_keypair_from_priv_bytes(keys[1]);
    keys[1].zeroize();
    keys[0].zeroize();

    log_info!("PeerId of node: {}", pubkey);

    //debug_println!("Private key of peer: {}", privkey.to_string());

    //let x_from_ed = pubkey.to_dh_from_ed();
    //log_info!("du Pubkey from ed: {}", x_from_ed);

    match config.unwrap() {
        DaemonConfig::V0(v0) => {
            run_server_v0(
                privkey,
                pubkey,
                SymKey::from_array(keys[2]),
                v0,
                path,
                args.invite_admin,
            )
            .await?
        }
    }

    Ok(())
}
