use std::env;
use std::sync::{Arc, Mutex};

#[allow(unused_imports)]
use log::{debug, error, info, trace, warn, LevelFilter};

use crate::blockchain::filter::BlockchainFilter;
use crate::dns::context::{ResolveStrategy, ServerContext};
use crate::dns::hosts::HostsFilter;
use crate::dns::server::{DnsServer, DnsTcpServer, DnsUdpServer};
use crate::{Context, Settings};

/// Starts UDP and TCP DNS-servers
pub fn start_dns_server(context: &Arc<Mutex<Context>>, settings: &Settings) -> bool {
    let server_context = create_server_context(Arc::clone(context), settings);

    let mut result = true;
    if server_context.enable_udp {
        let udp_server = DnsUdpServer::new(Arc::clone(&server_context), settings.dns.threads);
        if let Err(e) = udp_server.run_server() {
            error!("Failed to bind UDP listener on {}: {:?}", &server_context.dns_listen, e);
            result = false;
        }
    }

    if server_context.enable_tcp {
        let tcp_server = DnsTcpServer::new(Arc::clone(&server_context), settings.dns.threads);
        if let Err(e) = tcp_server.run_server() {
            error!("Failed to bind TCP listener on {}: {:?}", &server_context.dns_listen, e);
            result = false;
        }
    }
    result
}

/// Creates DNS-context with all needed settings
fn create_server_context(context: Arc<Mutex<Context>>, settings: &Settings) -> Arc<ServerContext> {
    let mut server_context = ServerContext::new(settings.dns.listen.clone(), settings.dns.bootstraps.clone());
    server_context.allow_recursive = true;
    server_context.resolve_strategy = match settings.dns.forwarders.is_empty() {
        true => ResolveStrategy::Recursive,
        false => ResolveStrategy::Forward { upstreams: settings.dns.forwarders.clone() }
    };
    // Add host filters
    for host in &settings.dns.hosts {
        if host == "system" {
            if cfg!(target_os = "windows") {
                if let Ok(root) = env::var("SYSTEMROOT") {
                    let filename = format!("{}{}", &root, "\\System32\\drivers\\etc\\hosts");
                    debug!("Loading hosts from '{}'", &filename);
                    server_context.filters.push(Box::new(HostsFilter::new(&filename)));
                }
            } else {
                let filename = "/etc/hosts";
                debug!("Loading hosts from '{}'", filename);
                server_context.filters.push(Box::new(HostsFilter::new(filename)));
            }
        } else {
            debug!("Loading hosts from '{}'", &host);
            server_context.filters.push(Box::new(HostsFilter::new(host)));
        }
    }
    server_context.filters.push(Box::new(BlockchainFilter::new(context)));
    match server_context.initialize() {
        Ok(_) => {}
        Err(e) => { panic!("DNS server failed to initialize: {:?}", e); }
    }

    Arc::new(server_context)
}
