use nanometrics_agent::{config, metrics, payload, transport};
use sysinfo::{Components, Networks, System};
use std::time::{Duration, Instant};
use std::sync::mpsc;
use std::sync::atomic::{AtomicBool, Ordering};

static RUNNING: AtomicBool = AtomicBool::new(true);

extern "C" fn handle_signal(_: libc::c_int) {
    RUNNING.store(false, Ordering::Relaxed);
}

fn get_local_ip(server_ip: &str) -> String {
    use std::net::UdpSocket;
    // Try server IP first (always reachable), then internet fallback
    for target in &[format!("{}:80", server_ip), "8.8.8.8:80".to_string()] {
        if let Ok(s) = UdpSocket::bind("0.0.0.0:0") {
            if s.connect(target.as_str()).is_ok() {
                if let Ok(addr) = s.local_addr() {
                    let ip = addr.ip().to_string();
                    if ip != "0.0.0.0" {
                        return ip;
                    }
                }
            }
        }
    }
    "0.0.0.0".to_string()
}

fn apply_config_update(cfg: &mut config::Config, data: &[u8]) {
    if let Ok(new_cfg) = serde_json::from_slice::<config::Config>(data) {
        cfg.metrics = new_cfg.metrics;
        eprintln!("[config] mis à jour depuis le serveur");
    }
}

fn main() {
    let cfg_path = std::env::args()
        .nth(1)
        .unwrap_or_else(|| "config.toml".to_string());
    let mut cfg = config::load(std::path::Path::new(&cfg_path))
        .expect("Impossible de charger config.toml");

    let hostname = System::host_name().unwrap_or_else(|| "unknown".to_string());
    let ip = get_local_ip(&cfg.server.ip);

    let mut sys = System::new();
    let mut networks = Networks::new_with_refreshed_list();
    let mut components = Components::new_with_refreshed_list();

    let udp_sender = if cfg.protocols.udp.enabled {
        Some(transport::udp::UdpSender::new(&cfg.server.ip, cfg.server.port))
    } else {
        None
    };

    let (incoming_tx, incoming_rx) = mpsc::channel::<transport::mqtt::MqttIncoming>();
    let mqtt_client = if cfg.protocols.mqtt.enabled {
        Some(transport::mqtt::start(&hostname, &cfg.protocols.mqtt, incoming_tx))
    } else {
        None
    };

    unsafe {
        libc::signal(libc::SIGTERM, handle_signal as libc::sighandler_t);
        libc::signal(libc::SIGINT,  handle_signal as libc::sighandler_t);
    }

    let mut last_slow = Instant::now();
    let mut last_medium = Instant::now();
    let mut first_medium = true;
    let mut first_slow = true;

    while RUNNING.load(Ordering::Relaxed) {
        let now = Instant::now();

        while let Ok(transport::mqtt::MqttIncoming::ConfigUpdate(data)) = incoming_rx.try_recv() {
            apply_config_update(&mut cfg, &data);
        }

        sys.refresh_cpu_usage();
        sys.refresh_memory();

        let mut m = payload::AgentMetrics {
            hostname: hostname.clone(),
            ip: ip.clone(),
            status: "online".to_string(),
            version: env!("CARGO_PKG_VERSION").to_string(),
            ..Default::default()
        };

        if cfg.metrics.cpu.udp || cfg.metrics.cpu.mqtt {
            m.cpu_percent = Some(metrics::cpu::get(&sys));
        }

        let (mem_used, mem_free, mem_total) = metrics::memory::get(&sys);
        if cfg.metrics.memory.udp || cfg.metrics.memory.mqtt {
            m.memory_used = Some(mem_used);
            m.memory_free = Some(mem_free);
            m.memory_total = Some(mem_total);
        }

        if first_medium || now.duration_since(last_medium).as_secs() >= 10 {
            networks.refresh();
            components.refresh();
            let (rx, tx) = metrics::network::get(&networks);
            if cfg.metrics.network.udp || cfg.metrics.network.mqtt {
                m.network_rx = Some(rx);
                m.network_tx = Some(tx);
            }
            if cfg.metrics.uptime.udp || cfg.metrics.uptime.mqtt {
                m.uptime = Some(metrics::uptime::get());
            }
            if cfg.metrics.temperature.udp || cfg.metrics.temperature.mqtt {
                m.temperature = metrics::temperature::get(&components);
            }
            last_medium = now;
            first_medium = false;
        }

        if first_slow || now.duration_since(last_slow).as_secs() >= 60 {
            if cfg.metrics.disk.udp || cfg.metrics.disk.mqtt {
                let (used, free, total) = metrics::disk::get();
                m.hdd_used = Some(used);
                m.hdd_free = Some(free);
                m.hdd_total = Some(total);
            }
            if cfg.metrics.smart.udp || cfg.metrics.smart.mqtt {
                m.smart = metrics::smart::collect();
            }
            last_slow = now;
            first_slow = false;
        }

        let json = serde_json::to_string(&m).expect("sérialisation JSON impossible");

        if let Some(ref udp) = udp_sender {
            udp.send(&json);
        }

        if let Some(ref client) = mqtt_client {
            if cfg.protocols.mqtt.enabled {
                transport::mqtt::publish_metrics(
                    client,
                    &cfg.protocols.mqtt.topic_base,
                    &hostname,
                    &json,
                );
            }
        }

        std::thread::sleep(Duration::from_secs(2));
    }

    // Déconnexion propre : notifier le serveur avant de quitter
    let offline = serde_json::to_string(&payload::AgentMetrics {
        hostname: hostname.clone(),
        ip: ip.clone(),
        status: "offline".to_string(),
        version: env!("CARGO_PKG_VERSION").to_string(),
        ..Default::default()
    }).unwrap_or_default();

    if let Some(ref udp) = udp_sender {
        udp.send(&offline);
    }
    if let Some(ref client) = mqtt_client {
        transport::mqtt::publish_status(
            client, &cfg.protocols.mqtt.topic_base, &hostname, "offline",
        );
        std::thread::sleep(Duration::from_millis(200)); // laisser le temps au broker de recevoir
        let _ = client.disconnect();
    }
}
