use std::collections::HashMap;
use std::process::Command;
use std::sync::atomic::{AtomicU32, Ordering};
use std::thread;

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

use crate::forge::Forge;

/// Events sent from the D-Bus thread to the main event loop.
#[derive(Debug)]
pub enum GnomeShellEvent {
    /// ShowOSD request: label, icon_name, percent (0.0-1.0)
    ShowOsd {
        label: String,
        level: f64,
    },
    /// Screenshot request.
    Screenshot {
        filename: String,
    },
    /// Grab accelerator request (the grab is stubbed; args are ignored).
    GrabAccelerator,
    UngrabAccelerator,
}

static NEXT_GRAB_ID: AtomicU32 = AtomicU32::new(1);

/// Start the GNOME Shell D-Bus compatibility server.
pub fn start_server(event_loop: &LoopHandle<'static, Forge>) {
    let (sender, channel) = channel::channel::<GnomeShellEvent>();

    event_loop
        .insert_source(channel, |event, _, forge| {
            if let channel::Event::Msg(evt) = event {
                match evt {
                    GnomeShellEvent::ShowOsd { label, level } => {
                        let percent = (level * 100.0).round() as u8;
                        let kind = if label.to_lowercase().contains("bright") {
                            crate::osd::OsdKind::Brightness
                        } else {
                            crate::osd::OsdKind::Volume
                        };
                        forge.osd.show(kind, percent, false);
                        forge.request_redraw();
                    }
                    GnomeShellEvent::Screenshot { filename } => {
                        // Use grim to take a screenshot
                        let _ = Command::new("grim").arg(&filename).spawn();
                    }
                    GnomeShellEvent::GrabAccelerator
                    | GnomeShellEvent::UngrabAccelerator => {
                        // Accelerator grabs are noted but not fully implemented
                        // (would need keymap integration)
                    }
                }
            }
        })
        .expect("gnome shell dbus channel");

    thread::Builder::new()
        .name("forge-gnome-shell-dbus".into())
        .spawn(move || {
            if let Err(e) = run_gnome_shell_dbus(sender) {
                error!("GNOME Shell D-Bus server failed: {e}");
            }
        })
        .expect("gnome shell dbus thread");
}

fn run_gnome_shell_dbus(
    sender: Sender<GnomeShellEvent>,
) -> Result<(), Box<dyn std::error::Error>> {
    info!("GNOME Shell D-Bus compatibility server starting");

    let shell_iface = ShellIface {
        sender: sender.clone(),
    };
    let screenshot_iface = ScreenshotIface {
        sender: sender.clone(),
    };

    let _conn = zbus::blocking::ConnectionBuilder::session()?
        .name("org.gnome.Shell")?
        .serve_at("/org/gnome/Shell", shell_iface)?
        .serve_at("/org/gnome/Shell/Screenshot", screenshot_iface)?
        .build()?;

    loop {
        std::thread::sleep(std::time::Duration::from_secs(60));
    }
}

// --- org.gnome.Shell interface ---

struct ShellIface {
    sender: Sender<GnomeShellEvent>,
}

#[zbus::interface(name = "org.gnome.Shell")]
impl ShellIface {
    fn show_osd(&self, params: HashMap<String, Value<'_>>) {
        let label = params
            .get("label")
            .and_then(|v| match v {
                Value::Str(s) => Some(s.as_str()),
                _ => None,
            })
            .unwrap_or("")
            .to_string();
        let level = params
            .get("level")
            .and_then(|v| match v {
                Value::F64(f) => Some(*f),
                Value::I32(i) => Some(*i as f64),
                Value::U32(u) => Some(*u as f64 / 100.0),
                _ => None,
            })
            .unwrap_or(0.0);

        let _ = self.sender.send(GnomeShellEvent::ShowOsd { label, level });
    }

    fn grab_accelerator(
        &self,
        _accelerator: String,
        _mode_flags: u32,
        _grab_flags: u32,
    ) -> u32 {
        let id = NEXT_GRAB_ID.fetch_add(1, Ordering::Relaxed);
        let _ = self.sender.send(GnomeShellEvent::GrabAccelerator);
        id
    }

    fn ungrab_accelerator(&self, _action: u32) -> bool {
        let _ = self.sender.send(GnomeShellEvent::UngrabAccelerator);
        true
    }

    #[zbus(property)]
    fn shell_version(&self) -> String {
        "45.0".to_string()
    }

    #[zbus(property)]
    fn overview_active(&self) -> bool {
        false
    }

    #[zbus(property)]
    fn set_overview_active(&self, _active: bool) {
        // No-op
    }
}

// --- org.gnome.Shell.Screenshot interface ---

struct ScreenshotIface {
    sender: Sender<GnomeShellEvent>,
}

#[zbus::interface(name = "org.gnome.Shell.Screenshot")]
impl ScreenshotIface {
    fn screenshot(
        &self,
        _include_cursor: bool,
        _flash: bool,
        filename: String,
    ) -> (bool, String) {
        let path = if filename.is_empty() {
            format!(
                "/tmp/screenshot-{}.png",
                std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)
                    .unwrap_or_default()
                    .as_secs()
            )
        } else {
            filename
        };

        let _ = self.sender.send(GnomeShellEvent::Screenshot {
            filename: path.clone(),
        });

        (true, path)
    }

    fn screenshot_window(
        &self,
        _include_frame: bool,
        include_cursor: bool,
        flash: bool,
        filename: String,
    ) -> (bool, String) {
        // For now, take a full screenshot (no window-specific support)
        self.screenshot(include_cursor, flash, filename)
    }

    fn screenshot_area(
        &self,
        x: i32,
        y: i32,
        width: i32,
        height: i32,
        _flash: bool,
        filename: String,
    ) -> (bool, String) {
        let path = if filename.is_empty() {
            format!(
                "/tmp/screenshot-{}.png",
                std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)
                    .unwrap_or_default()
                    .as_secs()
            )
        } else {
            filename
        };

        // Use grim with area selection
        let geometry = format!("{x},{y} {width}x{height}");
        let _ = Command::new("grim")
            .args(["-g", &geometry, &path])
            .spawn();

        (true, path)
    }
}
