//! lazydns - A DNS server implementation in Rust
//!
//! This is a Rust implementation of mosdns, aiming for 100% feature parity or better.
//!
//! # Features
//! - Full DNS protocol support (UDP, TCP, DoH, DoT, DoQ)
//! - Flexible plugin architecture
//! - High performance with Rust's zero-cost abstractions
//! - Comprehensive caching
//! - GeoIP/GeoSite support
//! - Full test and documentation coverage

mod cli;
use crate::cli::parse_args;
use lazydns::config::Config;
use lazydns::logging;
use lazydns::plugin::PluginBuilder;
use lazydns::server::ServerLauncher;
use std::sync::Arc;
#[cfg(unix)]
use tokio::signal::unix::{SignalKind, signal};
use tokio::sync::RwLock;
use tokio::time::{Duration, timeout};
use tracing::{debug, error, info};

// Command-line arguments are parsed in `src/cli.rs` using `pico-args`.

async fn wait_for_shutdown_signal() -> anyhow::Result<()> {
    #[cfg(unix)]
    {
        let mut sigterm = signal(SignalKind::terminate())?;
        let mut sighup = signal(SignalKind::hangup())?;

        tokio::select! {
            res = tokio::signal::ctrl_c() => {
                info!("Received Ctrl-C signal");
                res?;
            },
            _ = sigterm.recv() => {
                info!("Received SIGTERM signal");
            },
            _ = sighup.recv() => {
                info!("Received SIGHUP signal");
            },
        }
    }

    #[cfg(not(unix))]
    {
        tokio::signal::ctrl_c().await?;
    }

    Ok(())
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Parse command line arguments (prints help and returns early if requested)
    let args = match parse_args() {
        Some(a) => a,
        None => return Ok(()),
    };

    // Change working directory if specified (do this before loading config so relative
    // paths inside the config file behave as expected)
    if let Some(dir) = &args.dir {
        if let Err(e) = std::env::set_current_dir(dir) {
            error!("Failed to change working directory to {}: {}", dir, e);
            return Err(anyhow::anyhow!("Failed to change working directory"));
        }
        info!("Working directory changed to: {}", dir);
    }

    // Load configuration (before initializing logging so config can control logs)
    let config = match Config::from_file(&args.config) {
        Ok(config) => {
            println!("Configuration loaded successfully");
            config
        }
        Err(e) => {
            eprintln!("Failed to load configuration: {}", e);
            eprintln!("Using default configuration");
            Config::default()
        }
    };

    // Validate configuration
    if let Err(e) = config.validate() {
        eprintln!("Configuration validation warning: {}", e);
    }

    // Initialize logging: precedence handled in logging::effective_log_spec
    if let Err(e) = crate::logging::init_logging(&config.log, Some(args.verbose)) {
        eprintln!("Failed to initialize logging: {}", e);
    }

    info!("lazydns starting...");
    info!("Version: {}", env!("CARGO_PKG_VERSION"));
    info!("Configuration file: {}", args.config);

    // Ensure rustls has a process-level CryptoProvider installed (ring)
    #[cfg(feature = "rustls")]
    let _ = rustls::crypto::ring::default_provider().install_default();

    // Build plugins from configuration
    let mut builder = PluginBuilder::new();
    let mut plugin_count = 0;

    for plugin_config in &config.plugins {
        match builder.build(plugin_config) {
            Ok(_plugin) => {
                info!(
                    "Loaded plugin: {} (type: {})",
                    plugin_config.effective_name(),
                    plugin_config.plugin_type
                );
                plugin_count += 1;
            }
            Err(e) => {
                error!(
                    "Failed to load plugin {}: {}",
                    plugin_config.effective_name(),
                    e
                );
            }
        }
    }

    info!("Loaded {} plugins", plugin_count);

    // Resolve inter-plugin references (e.g., fallback primary/secondary plugin names)
    if let Err(e) = builder.resolve_references(&config.plugins) {
        error!("Failed to resolve plugin references: {}", e);
    }

    // Get the plugin registry for servers to use
    let registry = Arc::new(builder.get_registry());
    debug!(plugins = ?registry.plugin_names(), "Plugin registry contents");

    // Launch all configured servers using ServerLauncher
    let launcher = ServerLauncher::new(Arc::clone(&registry));
    let mut startup_receivers = launcher.launch_all(&config.plugins).await;

    // Launch admin API server if enabled
    let config_arc = Arc::new(RwLock::new(config.clone()));
    if let Some(rx) = launcher.launch_admin_server(Arc::clone(&config_arc)).await {
        startup_receivers.push(rx);
    }
    if let Some(rx) = launcher
        .launch_monitoring_server(Arc::clone(&config_arc))
        .await
    {
        startup_receivers.push(rx);
    }

    // Wait for all servers to start listening
    for rx in startup_receivers {
        let _ = rx.await; // Ignore errors - servers may have exited
    }

    // Give async server tasks a moment to start and output their listening messages
    tokio::time::sleep(Duration::from_millis(50)).await;

    info!("lazydns initialized successfully");

    // Wait for shutdown signal (Ctrl-C, SIGTERM, SIGHUP)
    if let Err(e) = wait_for_shutdown_signal().await {
        error!("Error waiting for shutdown signal: {}", e);
    }
    info!("Shutdown signal received, beginning graceful shutdown...");

    // Shutdown all plugins that implement the Shutdown trait, with timeout
    match timeout(Duration::from_secs(30), builder.shutdown_all()).await {
        Ok(Ok(())) => info!("Shutdown finished successfully"),
        Ok(Err(e)) => error!("Error during plugin shutdown: {}", e),
        Err(_) => error!("Shutdown timed out after 30s"),
    }

    // Monitoring server listens to OS signals itself for graceful shutdown.

    info!("lazydns exited normally");

    Ok(())
}
