use std::collections::HashMap;

use calloop::channel::Sender;
use calloop::LoopHandle;
use tracing::info;
use zbus::zvariant::{OwnedValue, Value};

use crate::forge::Forge;

const COLOR_SERVICE: &str = "org.gnome.SettingsDaemon.Color";
const COLOR_PATH: &str = "/org/gnome/SettingsDaemon/Color";
const COLOR_IFACE: &str = "org.gnome.SettingsDaemon.Color";
const DEFAULT_NIGHT_TEMPERATURE: u32 = 4500;

#[derive(Debug)]
pub enum NightLightEvent {
    Enabled(u32),
    Disabled,
    TemperatureChanged(u32),
}

pub fn start_listener(event_loop: &LoopHandle<'static, Forge>) {
    crate::services::dbus_worker::spawn_adapter(
        event_loop,
        "forge-night-light",
        run_night_light_listener,
        |ev, forge| match ev {
            NightLightEvent::Enabled(temp) => {
                if !forge.shell.sys_state.night_light_on {
                    forge.shell.sys_state.night_light_on = true;
                    forge.apply_night_light_gamma(temp);
                    forge.request_redraw();
                }
            }
            NightLightEvent::TemperatureChanged(temp) => {
                if forge.shell.sys_state.night_light_on {
                    forge.apply_night_light_gamma(temp);
                    forge.request_redraw();
                }
            }
            NightLightEvent::Disabled => {
                if forge.shell.sys_state.night_light_on {
                    forge.shell.sys_state.night_light_on = false;
                    forge.reset_gamma();
                    forge.request_redraw();
                }
            }
        },
    );
}

fn run_night_light_listener(
    sender: Sender<NightLightEvent>,
) -> Result<(), Box<dyn std::error::Error>> {
    info!("Night light D-Bus listener starting");

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

    if let Some(active) = read_active(&conn) {
        if active {
            let temp = read_temperature(&conn).unwrap_or(DEFAULT_NIGHT_TEMPERATURE);
            let _ = sender.send(NightLightEvent::Enabled(temp));
        }
    }

    let rule = zbus::MatchRule::builder()
        .msg_type(zbus::message::Type::Signal)
        .sender(COLOR_SERVICE)?
        .path(COLOR_PATH)?
        .interface("org.freedesktop.DBus.Properties")?
        .member("PropertiesChanged")?
        .build();

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

    while let Some(Ok(msg)) = iter.next() {
        let (iface, changed, _invalidated): (
            String,
            HashMap<String, OwnedValue>,
            Vec<String>,
        ) = match msg.body().deserialize() {
            Ok(t) => t,
            Err(_) => continue,
        };

        if iface != COLOR_IFACE {
            continue;
        }

        let active = changed.get("NightLightActive").and_then(value_as_bool);
        let temp = changed.get("Temperature").and_then(value_as_u32);

        match (active, temp) {
            (Some(true), Some(t)) => {
                let _ = sender.send(NightLightEvent::Enabled(t));
            }
            (Some(true), None) => {
                let t = read_temperature(&conn).unwrap_or(DEFAULT_NIGHT_TEMPERATURE);
                let _ = sender.send(NightLightEvent::Enabled(t));
            }
            (Some(false), _) => {
                let _ = sender.send(NightLightEvent::Disabled);
            }
            (None, Some(t)) => {
                let _ = sender.send(NightLightEvent::TemperatureChanged(t));
            }
            (None, None) => {}
        }
    }

    Ok(())
}

fn read_active(conn: &zbus::blocking::Connection) -> Option<bool> {
    read_property(conn, "NightLightActive").and_then(|v| value_as_bool(&v))
}

fn read_temperature(conn: &zbus::blocking::Connection) -> Option<u32> {
    read_property(conn, "Temperature").and_then(|v| value_as_u32(&v))
}

fn read_property(conn: &zbus::blocking::Connection, name: &str) -> Option<OwnedValue> {
    let reply = conn
        .call_method(
            Some(COLOR_SERVICE),
            COLOR_PATH,
            Some("org.freedesktop.DBus.Properties"),
            "Get",
            &(COLOR_IFACE, name),
        )
        .ok()?;
    reply.body().deserialize::<OwnedValue>().ok()
}

fn value_as_bool(v: &OwnedValue) -> Option<bool> {
    match &**v {
        Value::Bool(b) => Some(*b),
        _ => None,
    }
}

fn value_as_u32(v: &OwnedValue) -> Option<u32> {
    match &**v {
        Value::U32(t) => Some(*t),
        _ => None,
    }
}
