use std::fs;
use std::path::Path;
use std::sync::mpsc;

use crate::args::Args;
use rns_net::storage;
use rns_net::{Callbacks, InterfaceId, RnsNode};

const VERSION: &str = env!("FULL_VERSION");

struct DaemonCallbacks;

impl Callbacks for DaemonCallbacks {
    fn on_announce(&mut self, announced: rns_net::AnnouncedIdentity) {
        let rssi = match announced.rssi {
            Some(x) => format!(", rssi:{}", x),
            None => "".to_string(),
        };
        let snr = match announced.snr {
            Some(x) => format!(", snr:{}", x),
            None => "".to_string(),
        };
        log::info!(
            "Announce received for {} (hops: {}{}{})",
            hex(&announced.dest_hash.0),
            announced.hops,
            rssi,
            snr,
        );
    }

    fn on_path_updated(&mut self, dest_hash: rns_net::DestHash, hops: u8) {
        log::debug!("Path updated for {} (hops: {})", hex(&dest_hash.0), hops);
    }

    fn on_local_delivery(
        &mut self,
        dest_hash: rns_net::DestHash,
        _raw: Vec<u8>,
        _hash: rns_net::PacketHash,
    ) {
        log::debug!("Local delivery for {}", hex(&dest_hash.0));
    }

    fn on_interface_up(&mut self, id: InterfaceId) {
        log::info!("Interface {} up", id.0);
    }

    fn on_interface_down(&mut self, id: InterfaceId) {
        log::info!("Interface {} down", id.0);
    }
}

pub fn main_entry() {
    main_entry_from(Args::parse());
}

pub fn main_entry_from(args: Args) {
    if args.has("version") {
        println!("rnsd {}", VERSION);
        return;
    }

    if args.has("help") || args.has("h") {
        print_usage();
        return;
    }

    if args.has("exampleconfig") {
        print!("{}", EXAMPLE_CONFIG);
        return;
    }

    let service_mode = args.has("s");
    let config_path = args.config_path().map(|s| s.to_string());
    let log_timestamps = configured_log_timestamps(config_path.as_deref());

    let log_level = match args.verbosity {
        0 => log::LevelFilter::Info,
        1 => log::LevelFilter::Debug,
        _ => log::LevelFilter::Trace,
    };
    let log_level = if args.quiet > 0 {
        match args.quiet {
            1 => log::LevelFilter::Warn,
            _ => log::LevelFilter::Error,
        }
    } else {
        log_level
    };

    if service_mode {
        let config_dir =
            storage::resolve_config_dir(config_path.as_ref().map(|s| Path::new(s.as_str())));
        let logfile_path = config_dir.join("logfile");
        match fs::OpenOptions::new()
            .create(true)
            .append(true)
            .open(&logfile_path)
        {
            Ok(file) => {
                let mut builder = env_logger::Builder::new();
                builder
                    .filter_level(log_level)
                    .target(env_logger::Target::Pipe(Box::new(file)));
                apply_log_timestamp_format(&mut builder, log_timestamps);
                builder.init();
            }
            Err(e) => {
                eprintln!("Could not open logfile {}: {}", logfile_path.display(), e);
                std::process::exit(1);
            }
        }
    } else {
        let mut builder = env_logger::Builder::new();
        builder.filter_level(log_level);
        apply_log_timestamp_format(&mut builder, log_timestamps);
        builder.init();
    }

    log::info!("Starting rnsd {}", VERSION);
    if let Err(err) = register_native_sidecar_hooks() {
        log::error!("Failed to register built-in sidecar hooks: {}", err);
        std::process::exit(1);
    }

    let node = RnsNode::from_config(
        config_path.as_ref().map(|s| Path::new(s.as_str())),
        Box::new(DaemonCallbacks),
    );

    let node = match node {
        Ok(n) => n,
        Err(e) => {
            log::error!("Failed to start: {}", e);
            std::process::exit(1);
        }
    };

    let (stop_tx, stop_rx) = mpsc::channel::<()>();

    unsafe {
        libc::signal(
            libc::SIGINT,
            signal_handler as *const () as libc::sighandler_t,
        );
        libc::signal(
            libc::SIGTERM,
            signal_handler as *const () as libc::sighandler_t,
        );
    }
    lock_stop_tx().replace(stop_tx);

    log::info!("rnsd started");

    loop {
        match stop_rx.recv_timeout(std::time::Duration::from_secs(1)) {
            Ok(()) => break,
            Err(std::sync::mpsc::RecvTimeoutError::Timeout) => continue,
            Err(_) => break,
        }
    }

    log::info!("Shutting down...");
    node.shutdown();
    log::info!("rnsd stopped");
}

fn configured_log_timestamps(config_path: Option<&str>) -> bool {
    let config_dir = storage::resolve_config_dir(config_path.map(Path::new));
    let config_file = config_dir.join("config");
    if !config_file.exists() {
        return true;
    }

    match rns_net::config::parse_file(&config_file) {
        Ok(config) => config.logging.logtimestamps,
        Err(err) => {
            eprintln!(
                "Could not parse logging config {}: {}",
                config_file.display(),
                err
            );
            true
        }
    }
}

fn apply_log_timestamp_format(builder: &mut env_logger::Builder, include_timestamps: bool) {
    if include_timestamps {
        builder.format_timestamp_secs();
    } else {
        builder.format_timestamp(None);
    }
}

#[cfg(any(feature = "rns-hooks-native", feature = "rns-hooks-builtin"))]
fn register_native_sidecar_hooks() -> Result<(), String> {
    rns_stats_hook::register_builtin_hooks()
        .map_err(|err| format!("stats hook registration failed: {}", err))?;
    rns_sentinel_hook::register_builtin_hooks()
        .map_err(|err| format!("sentinel hook registration failed: {}", err))?;
    Ok(())
}

#[cfg(not(any(feature = "rns-hooks-native", feature = "rns-hooks-builtin")))]
fn register_native_sidecar_hooks() -> Result<(), String> {
    Ok(())
}

fn hex(bytes: &[u8]) -> String {
    bytes.iter().map(|b| format!("{:02x}", b)).collect()
}

static STOP_TX: std::sync::Mutex<Option<mpsc::Sender<()>>> = std::sync::Mutex::new(None);

fn lock_stop_tx() -> std::sync::MutexGuard<'static, Option<mpsc::Sender<()>>> {
    match STOP_TX.lock() {
        Ok(guard) => guard,
        Err(poisoned) => {
            log::warn!("recovering poisoned rnsd stop channel mutex");
            poisoned.into_inner()
        }
    }
}

extern "C" fn signal_handler(_sig: libc::c_int) {
    let guard = lock_stop_tx();
    if let Some(ref tx) = *guard {
        let _ = tx.send(());
    }
}

fn print_usage() {
    println!("Usage: rnsd [OPTIONS]");
    println!();
    println!("Options:");
    println!("  --config PATH, -c PATH  Path to config directory");
    println!("  -s                      Service mode (log to file)");
    println!("  --exampleconfig         Print example config and exit");
    println!("  -v                      Increase verbosity (can repeat)");
    println!("  -q                      Decrease verbosity (can repeat)");
    println!("  --version               Print version and exit");
    println!("  --help, -h              Print this help");
}

const EXAMPLE_CONFIG: &str = r#"# This is an example Reticulum config file.
# It can be used as a starting point for your own configuration.

[reticulum]
  enable_transport = false
  share_instance = true
  shared_instance_port = 37428
  instance_control_port = 37429
  panic_on_interface_error = false

[logging]
  loglevel = 4

  # Disable timestamp inclusion when an external logging tool
  # provides its own timestamps or formatting.
  # logtimestamps = no

# ─── Interface examples ──────────────────────────────────────────────

# TCP client: connect to a remote transport node
#
# [[TCP Client]]
#   type = TCPClientInterface
#   target_host = amsterdam.connect.reticulum.network
#   target_port = 4965

# TCP server: accept incoming connections
#
# [[TCP Server]]
#   type = TCPServerInterface
#   listen_ip = 0.0.0.0
#   listen_port = 4965

# UDP interface: broadcast on LAN
#
# [[UDP Interface]]
#   type = UDPInterface
#   listen_ip = 0.0.0.0
#   listen_port = 4242
#   forward_ip = 255.255.255.255
#   forward_port = 4242

# Serial interface: point-to-point serial port
#
# [[Serial Interface]]
#   type = SerialInterface
#   port = /dev/ttyUSB0
#   speed = 115200
#   databits = 8
#   parity = none
#   stopbits = 1

# KISS interface: for TNC modems
#
# [[KISS Interface]]
#   type = KISSInterface
#   port = /dev/ttyUSB1
#   speed = 115200
#   databits = 8
#   parity = none
#   stopbits = 1
#   preamble = 350
#   txtail = 20
#   persistence = 64
#   slottime = 20
#   flow_control = false

# RNode LoRa interface
#
# [[RNode LoRa Interface]]
#   type = RNodeInterface
#   port = /dev/ttyACM0
#   frequency = 867200000
#   bandwidth = 125000
#   txpower = 7
#   spreadingfactor = 8
#   codingrate = 5

# Pipe interface: stdin/stdout of a subprocess
#
# [[Pipe Interface]]
#   type = PipeInterface
#   command = cat

# Backbone interface: TCP mesh
#
# [[Backbone]]
#   type = BackboneInterface
#   listen_ip = 0.0.0.0
#   listen_port = 4243
#   peers = 10.0.0.1:4243, 10.0.0.2:4243
"#;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn configured_log_timestamps_defaults_to_enabled() {
        let dir = tempfile::tempdir().unwrap();

        assert!(configured_log_timestamps(Some(
            dir.path().to_str().unwrap()
        )));
    }

    #[test]
    fn configured_log_timestamps_reads_logging_section() {
        let dir = tempfile::tempdir().unwrap();
        fs::write(dir.path().join("config"), "[logging]\nlogtimestamps = no\n").unwrap();

        assert!(!configured_log_timestamps(Some(
            dir.path().to_str().unwrap()
        )));
    }
}
