// SPDX-FileCopyrightText: (c) 2026 brew-proxy contributors <https://codeberg.org/HastD/brew-proxy>
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Client-side Polkit agent interaction. This is so we can use `pkttyagent` as a fallback in case a
//! graphical Polkit agent isn't available (for example in an environment with no DE/WM).

use std::{os::fd::AsRawFd, time::Duration};

use anyhow::Result;
use nix::{
    fcntl::{FcntlArg, FdFlag, fcntl},
    sys::signal::{Signal, kill},
    unistd::{Pid, Uid},
};
use tokio::{
    net::unix::pipe,
    process::{Child, Command},
    time::timeout,
};
use tracing::debug;

pub async fn start_agent() -> Result<Option<Child>> {
    if Uid::effective().is_root() {
        debug!("User is running as root; Polkit agent not needed.");
        return Ok(None);
    }
    let Ok(pkttyagent) = which::which_global("pkttyagent") else {
        debug!("pkttyagent not found; running without fallback textual Polkit agent.");
        return Ok(None);
    };
    debug!("Starting fallback textual Polkit agent");
    // Open pipe for pkttyagent to signal when the agent has been registered
    let (notify_sender, mut notify_receiver) = pipe::pipe()?;
    let notify_sender = notify_sender.into_blocking_fd()?;
    // Remove the FD_CLOEXEC flag from the fd so it's inherited by pkttyagent
    fcntl(&notify_sender, FcntlArg::F_SETFD(FdFlag::empty()))?;
    let child = Command::new(pkttyagent)
        .arg("--notify-fd")
        .arg(notify_sender.as_raw_fd().to_string())
        .arg("--fallback")
        .kill_on_drop(true)
        .spawn()?;
    // Drop our copy of the sender fd so it doesn't hold the receiver open
    drop(notify_sender);
    // Wait for EOF to indicate the agent is registered
    tokio::io::copy(&mut notify_receiver, &mut tokio::io::sink()).await?;
    debug!("Fallback textual Polkit agent started: {child:?}");
    Ok(Some(child))
}

pub async fn stop_agent(agent: Option<Child>) -> Result<()> {
    if let Some(mut agent) = agent
        && let Some(pid) = agent.id()
    {
        debug!("Sending SIGTERM to textual Polkit agent with PID {pid}.");
        let _ = kill(Pid::from_raw(pid as _), Some(Signal::SIGTERM))
            .inspect_err(|err| debug!("Error sending SIGTERM to pkttyagent: {err}"));
        if let Ok(result) = timeout(Duration::from_secs(3), agent.wait()).await {
            result?;
        } else {
            debug!("Textual Polkit agent with PID {pid} is unresponsive; sending SIGKILL.");
            agent.kill().await?;
        }
    }
    Ok(())
}
