#![allow(clippy::future_not_send)]

use std::time::Duration;

use anyhow::{bail, Context};
use async_io::Timer;
use futures_lite::{stream::StreamExt, FutureExt};
use zbus::{proxy, zvariant::ObjectPath, Connection, Result};

use crate::geo::Coords;

#[proxy(
    default_service = "org.freedesktop.GeoClue2",
    interface = "org.freedesktop.GeoClue2.Manager",
    default_path = "/org/freedesktop/GeoClue2/Manager"
)]
trait GeoClueManager {
    #[zbus(object = "GeoClueClient")]
    fn get_client(&self);
}

#[proxy(
    default_service = "org.freedesktop.GeoClue2",
    interface = "org.freedesktop.GeoClue2.Client"
)]
trait GeoClueClient {
    fn start(&self) -> Result<()>;
    fn stop(&self) -> Result<()>;

    #[zbus(property)]
    fn set_desktop_id(&self, desktop_id: &str) -> Result<()>;

    #[zbus(signal)]
    fn location_updated(&self, old: ObjectPath<'_>, new: ObjectPath<'_>) -> Result<()>;
}

#[proxy(
    default_service = "org.freedesktop.GeoClue2",
    interface = "org.freedesktop.GeoClue2.Location"
)]
trait GeoClueLocation {
    #[zbus(property)]
    fn latitude(&self) -> Result<f64>;
    #[zbus(property)]
    fn longitude(&self) -> Result<f64>;
}

async fn get_location_async() -> anyhow::Result<Coords> {
    log::debug!("Trying to get location from GeoClue");

    let connection = Connection::system()
        .await
        .context("couldn't connect to dbus")?;
    let manager = GeoClueManagerProxy::new(&connection)
        .await
        .context("couldn't create GeoClue manager")?;
    let client = manager
        .get_client()
        .await
        .context("couldn't get CeoClue client")?;

    client.set_desktop_id("timewall").await?;
    let mut location_updated = client.receive_location_updated().await?;
    client.start().await?;

    let signal = location_updated.next().await.unwrap();
    let args = signal.args()?;
    let location = GeoClueLocationProxy::builder(&connection)
        .path(args.new())?
        .build()
        .await?;

    client.stop().await?;

    let coords = Coords {
        lat: location.latitude().await?,
        lon: location.longitude().await?,
    };

    log::debug!("Got location from GeoClue: {coords:?}");

    Ok(coords)
}

pub fn get_location(timeout: Duration) -> anyhow::Result<Coords> {
    async_io::block_on(get_location_async().or(async {
        Timer::after(timeout).await;
        bail!("timed out while waiting for location from GeoClue")
    }))
}
