use std::thread;

use calloop::channel::{self, Sender};
use calloop::LoopHandle;
use tracing::{error, info};

use crate::forge::Forge;

/// Events from hostname1 D-Bus.
#[derive(Debug)]
pub enum HostnameEvent {
    /// Hostname resolved.
    HostnameResolved(String),
}

/// Start the hostname1 D-Bus listener.
pub fn start_listener(event_loop: &LoopHandle<'static, Forge>) {
    let (sender, channel) = channel::channel::<HostnameEvent>();

    event_loop
        .insert_source(channel, |event, _, forge| {
            if let calloop::channel::Event::Msg(ev) = event {
                match ev {
                    HostnameEvent::HostnameResolved(name) => {
                        forge.sys_state.hostname = name;
                    }
                }
            }
        })
        .expect("hostname channel");

    thread::Builder::new()
        .name("forge-hostname".into())
        .spawn(move || {
            if let Err(e) = run_hostname_listener(sender) {
                error!("Hostname listener failed: {e}");
            }
        })
        .expect("hostname thread");
}

fn run_hostname_listener(
    sender: Sender<HostnameEvent>,
) -> Result<(), Box<dyn std::error::Error>> {
    info!("Hostname D-Bus listener starting");

    let conn = zbus::blocking::Connection::system()?;

    let hostname = get_hostname(&conn);
    let _ = sender.send(HostnameEvent::HostnameResolved(hostname));

    // Hostname rarely changes, poll infrequently
    loop {
        std::thread::sleep(std::time::Duration::from_secs(300));
        let hostname = get_hostname(&conn);
        let _ = sender.send(HostnameEvent::HostnameResolved(hostname));
    }
}

fn get_hostname(conn: &zbus::blocking::Connection) -> String {
    let reply = conn.call_method(
        Some("org.freedesktop.hostname1"),
        "/org/freedesktop/hostname1",
        Some("org.freedesktop.DBus.Properties"),
        "Get",
        &("org.freedesktop.hostname1", "Hostname"),
    );

    match reply {
        Ok(msg) => msg
            .body()
            .deserialize::<zbus::zvariant::Value>()
            .ok()
            .and_then(|v| match v {
                zbus::zvariant::Value::Str(s) => Some(s.to_string()),
                _ => None,
            })
            .unwrap_or_else(|| gethostname_fallback()),
        Err(_) => gethostname_fallback(),
    }
}

fn gethostname_fallback() -> String {
    std::fs::read_to_string("/etc/hostname")
        .unwrap_or_else(|_| "localhost".to_string())
        .trim()
        .to_string()
}
