use std::thread;
use std::time::Duration;

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

use crate::forge::Forge;

/// Events from GeoClue2 location service.
#[derive(Debug)]
pub enum LocationEvent {
    /// Location service availability changed.
    AvailabilityChanged(bool),
}

/// Start the GeoClue2 D-Bus listener for location services status.
pub fn start_listener(event_loop: &LoopHandle<'static, Forge>) {
    let (sender, channel) = channel::channel::<LocationEvent>();

    event_loop
        .insert_source(channel, |event, _, forge| {
            if let calloop::channel::Event::Msg(ev) = event {
                match ev {
                    LocationEvent::AvailabilityChanged(available) => {
                        forge.sys_state.location_enabled = available;
                        forge.windows.set_dirty();
                    }
                }
            }
        })
        .expect("geoclue channel");

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

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

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

    // Register as a GeoClue agent to allow apps to use location
    register_agent(&conn);

    loop {
        let available = check_location_available(&conn);
        let _ = sender.send(LocationEvent::AvailabilityChanged(available));
        thread::sleep(Duration::from_secs(30));
    }
}

fn register_agent(conn: &zbus::blocking::Connection) {
    // Try to register as a GeoClue2 agent
    let _ = conn.call_method(
        Some("org.freedesktop.GeoClue2"),
        "/org/freedesktop/GeoClue2/Manager",
        Some("org.freedesktop.GeoClue2.Manager"),
        "AddAgent",
        &("forge"),
    );
}

fn check_location_available(conn: &zbus::blocking::Connection) -> bool {
    let reply = conn.call_method(
        Some("org.freedesktop.GeoClue2"),
        "/org/freedesktop/GeoClue2/Manager",
        Some("org.freedesktop.DBus.Properties"),
        "Get",
        &("org.freedesktop.GeoClue2.Manager", "InUse"),
    );

    match reply {
        Ok(msg) => msg
            .body()
            .deserialize::<zbus::zvariant::Value>()
            .ok()
            .and_then(|v| match v {
                zbus::zvariant::Value::Bool(b) => Some(b),
                _ => None,
            })
            .unwrap_or(false),
        Err(_) => false,
    }
}
