use std::collections::HashMap;
use std::env;
use std::sync::LazyLock;
use zbus::zvariant::{OwnedValue, Value};
use zbus::{fdo, message::Header, Connection, Proxy};

/// Whether or not polkit should be used for action authorization.
static POLKIT_DISABLED: LazyLock<bool> = LazyLock::new(|| {
    let is_disabled = env::var("INSECURE_DISABLE_POLKIT")
        .map(|v| v.as_str() == "1" || v.as_str().to_lowercase() == "true")
        .unwrap_or(false);
    if is_disabled {
        log::warn!("Running without polkit authorization");
    }
    is_disabled
});

pub async fn check_polkit(
    connection: &Connection,
    hdr: Option<Header<'_>>,
    action_id: &str,
) -> fdo::Result<()> {
    if *POLKIT_DISABLED {
        return Ok(());
    }
    let Some(hdr) = hdr else {
        return Ok(());
    };

    let sender = hdr
        .sender()
        .ok_or_else(|| fdo::Error::Failed("Missing sender".into()))?;

    let mut subj_details: HashMap<String, OwnedValue> = HashMap::new();
    subj_details.insert(
        "name".into(),
        OwnedValue::try_from(Value::Str(sender.as_str().into()))
            .map_err(|e| fdo::Error::Failed(format!("Failed to get OwnedValue for name: {e}")))?,
    );
    let subject = ("system-bus-name".to_string(), subj_details);

    let proxy = Proxy::new(
        connection,
        "org.freedesktop.PolicyKit1",
        "/org/freedesktop/PolicyKit1/Authority",
        "org.freedesktop.PolicyKit1.Authority",
    )
    .await
    .map_err(|e| fdo::Error::Failed(e.to_string()))?;

    let call_details: HashMap<String, String> = HashMap::new();
    let flags: u32 = 0x0000_0001;
    let cancellation_id = "";

    let (is_authorized, is_challenge, _details): (bool, bool, HashMap<String, String>) = proxy
        .call_method(
            "CheckAuthorization",
            &(subject, action_id, &call_details, flags, cancellation_id),
        )
        .await
        .map_err(|e| fdo::Error::Failed(e.to_string()))?
        .body()
        .deserialize()?;
    if is_authorized {
        Ok(())
    } else if is_challenge {
        Err(fdo::Error::InteractiveAuthorizationRequired(format!(
            "Authentication required for {}",
            action_id
        )))
    } else {
        Err(fdo::Error::Failed(format!(
            "Not authorized for {}",
            action_id
        )))
    }
}
