//! # D-Bus interface proxy for: `org.freedesktop.systemd1.Manager`
use anyhow::{Context, bail};
use relm4::tokio::process::Command;
use zbus::{proxy, zvariant::OwnedObjectPath};

pub const UNIT_NAME: &str = "lactd.service";

pub const UNIT_STATE_ACTIVE: &str = "active";

pub const START_MODE_REPLACE: &str = "replace";

pub async fn connect_unit_proxy() -> anyhow::Result<(ManagerProxy<'static>, UnitProxy<'static>)> {
    let conn = zbus::Connection::system()
        .await
        .context("Could not establish DBus connection")?;

    let manager = ManagerProxy::new(&conn)
        .await
        .context("Could not connect to systemd manager interface")?;

    let path = manager
        .load_unit(UNIT_NAME)
        .await
        .context("Could not get lact systemd unit")?;

    let unit = UnitProxy::builder(&conn)
        .path(path)?
        .build()
        .await
        .context("Could not connect to systemd unit interface")?;

    Ok((manager, unit))
}

#[proxy(
    interface = "org.freedesktop.systemd1.Manager",
    default_service = "org.freedesktop.systemd1",
    default_path = "/org/freedesktop/systemd1"
)]
pub trait Manager {
    #[zbus(allow_interactive_auth)]
    fn load_unit(&self, name: &str) -> zbus::Result<OwnedObjectPath>;

    #[zbus(allow_interactive_auth)]
    fn enable_unit_files(
        &self,
        files: &[&str],
        runtime: bool,
        force: bool,
    ) -> zbus::Result<(bool, Vec<(String, String, String)>)>;

    #[zbus(allow_interactive_auth)]
    fn disable_unit_files(
        &self,
        files: &[&str],
        runtime: bool,
    ) -> zbus::Result<Vec<(String, String, String)>>;
}

#[proxy(
    interface = "org.freedesktop.systemd1.Unit",
    default_service = "org.freedesktop.systemd1"
)]
pub trait Unit {
    #[zbus(allow_interactive_auth)]
    fn restart(&self, mode: &str) -> zbus::Result<OwnedObjectPath>;

    #[zbus(allow_interactive_auth)]
    fn start(&self, mode: &str) -> zbus::Result<OwnedObjectPath>;

    #[zbus(allow_interactive_auth)]
    fn stop(&self, mode: &str) -> zbus::Result<OwnedObjectPath>;

    #[zbus(property)]
    fn active_state(&self) -> zbus::Result<String>;
}

pub async fn fetch_logs() -> anyhow::Result<String> {
    let output = Command::new("journalctl")
        .args([
            "-I",
            "--reverse",
            "--no-hostname",
            "--no-pager",
            "-o",
            "short",
            "-u",
            UNIT_NAME,
        ])
        .output()
        .await
        .context("Could not run journalctl")?;

    if !output.status.success() {
        bail!("journalctl exited with status {}", output.status);
    }

    String::from_utf8(output.stdout).context("Could not parse journalctl output")
}
