//! org.freedesktop.login1 (logind) D-Bus integration.
//!
//! Handles session management: lock/unlock signals, suspend inhibitors,
//! seat management, and idle hints.

use std::thread;

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

use crate::forge::Forge;

/// Events sent from the logind listener to the main event loop.
#[derive(Debug)]
pub enum LogindEvent {
    /// Session locked externally (e.g., loginctl lock-session).
    Lock,
    /// Session unlocked externally.
    Unlock,
    /// System is about to suspend — prepare.
    PrepareForSleep(bool),
}

/// Returns true if the current logind session type is "greeter".
pub fn is_greeter_session() -> bool {
    let Ok(conn) = zbus::blocking::Connection::system() else { return false };
    let session_id = std::env::var("XDG_SESSION_ID").unwrap_or_else(|_| "auto".into());
    let Ok(reply) = conn.call_method(
        Some("org.freedesktop.login1"),
        "/org/freedesktop/login1",
        Some("org.freedesktop.login1.Manager"),
        "GetSession",
        &(&session_id,),
    ) else { return false };
    let Ok(session_path) = reply.body().deserialize::<zbus::zvariant::OwnedObjectPath>() else {
        return false;
    };
    let Ok(reply) = conn.call_method(
        Some("org.freedesktop.login1"),
        session_path.as_str(),
        Some("org.freedesktop.DBus.Properties"),
        "Get",
        &("org.freedesktop.login1.Session", "Class"),
    ) else { return false };
    let Ok((value,)) = reply.body().deserialize::<(zbus::zvariant::OwnedValue,)>() else {
        return false;
    };
    String::try_from(value).as_deref() == Ok("greeter")
}

/// Start the logind D-Bus listener.
pub fn start_listener(event_loop: &LoopHandle<'static, Forge>) {
    let (sender, channel) = channel::channel::<LogindEvent>();

    event_loop
        .insert_source(channel, |event, _, forge| {
            if let calloop::channel::Event::Msg(ev) = event {
                match ev {
                    LogindEvent::Lock => {
                        info!("logind: lock requested");
                        forge.lock_session();
                        forge.request_redraw();
                    }
                    LogindEvent::Unlock => {
                        info!("logind: unlock requested");
                    }
                    LogindEvent::PrepareForSleep(start) => {
                        if start {
                            info!("logind: preparing for sleep");
                            forge.lock_session();
                            forge.set_display_status(false);
                        } else {
                            info!("logind: resumed from sleep");
                            forge.set_display_status(true);
                            forge.force_redraw(true);
                        }
                    }
                }
            }
        })
        .expect("logind D-Bus channel");

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

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

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

    // Get the session path for the current session
    let session_id = std::env::var("XDG_SESSION_ID").unwrap_or_else(|_| "auto".into());
    let reply = conn.call_method(
        Some("org.freedesktop.login1"),
        "/org/freedesktop/login1",
        Some("org.freedesktop.login1.Manager"),
        "GetSession",
        &(&session_id,),
    )?;
    let session_path: zbus::zvariant::OwnedObjectPath = reply.body().deserialize()?;

    info!("logind session: {}", session_path.as_str());

    // Also set idle hint so logind knows we're managing idle
    let _ = conn.call_method(
        Some("org.freedesktop.login1"),
        session_path.as_str(),
        Some("org.freedesktop.login1.Session"),
        "SetIdleHint",
        &(false,),
    );

    // Build a match rule that captures all logind signals we care about
    let rule = zbus::MatchRule::builder()
        .msg_type(zbus::message::Type::Signal)
        .sender("org.freedesktop.login1")?
        .build();

    let mut iter =
        zbus::blocking::MessageIterator::for_match_rule(rule, &conn, Some(16))?;

    while let Some(Ok(msg)) = iter.next() {
        let header = msg.header();
        let member = match header.member() {
            Some(m) => m,
            None => continue,
        };
        let path = header.path().map(|p| p.as_str().to_owned()).unwrap_or_default();

        match member.as_str() {
            "Lock" if path == session_path.as_str() => {
                let _ = sender.send(LogindEvent::Lock);
            }
            "Unlock" if path == session_path.as_str() => {
                let _ = sender.send(LogindEvent::Unlock);
            }
            "PrepareForSleep" => {
                if let Ok(start) = msg.body().deserialize::<(bool,)>() {
                    let _ = sender.send(LogindEvent::PrepareForSleep(start.0));
                }
            }
            _ => {}
        }
    }

    Ok(())
}
