//! xdg-desktop-portal backend D-Bus interfaces.
//!
//! Implements the portal interfaces needed for Flatpak applications:
//! - org.freedesktop.impl.portal.Screenshot
//! - org.freedesktop.impl.portal.ScreenCast

use std::collections::HashMap;
use std::thread;

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

use crate::forge::Forge;

/// Events from the portal D-Bus.
#[derive(Debug)]
pub enum PortalEvent {
    /// Screenshot requested via portal.
    ScreenshotRequest,
    /// Screencast started (wf-recorder or similar).
    ScreencastStart,
}

/// Start the xdg-desktop-portal backend D-Bus server.
pub fn start_server(event_loop: &LoopHandle<'static, Forge>) {
    let (sender, channel) = channel::channel::<PortalEvent>();

    event_loop
        .insert_source(channel, |event, _, _forge| {
            if let calloop::channel::Event::Msg(ev) = event {
                match ev {
                    PortalEvent::ScreenshotRequest => {
                        let ts = chrono_timestamp();
                        let path = screenshot_path(&ts);
                        let _ = std::process::Command::new("grim").arg(&path).spawn();
                        info!("Portal screenshot saved to {path}");
                    }
                    PortalEvent::ScreencastStart => {
                        let ts = chrono_timestamp();
                        let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".into());
                        let dir = format!("{home}/Videos");
                        let _ = std::fs::create_dir_all(&dir);
                        let path = format!("{dir}/Screencast_{ts}.mp4");
                        let _ = std::process::Command::new("wf-recorder")
                            .args(["-f", &path])
                            .spawn();
                        info!("Portal screencast recording to {path}");
                    }
                }
            }
        })
        .expect("portal D-Bus channel");

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

fn chrono_timestamp() -> String {
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs();
    format!("{now}")
}

fn screenshot_path(ts: &str) -> String {
    let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".into());
    let dir = format!("{home}/Screenshots");
    let _ = std::fs::create_dir_all(&dir);
    format!("{dir}/Screenshot_{ts}.png")
}

fn run_portal_dbus(
    sender: Sender<PortalEvent>,
) -> Result<(), Box<dyn std::error::Error>> {
    info!("Portal D-Bus backend starting");

    let screenshot_iface = ScreenshotPortalIface {
        sender: sender.clone(),
    };
    let screencast_iface = ScreenCastPortalIface {
        sender: sender.clone(),
    };

    let _conn = zbus::blocking::ConnectionBuilder::session()?
        .name("org.freedesktop.impl.portal.desktop.forge")?
        .serve_at(
            "/org/freedesktop/portal/desktop/forge",
            screenshot_iface,
        )?
        .serve_at(
            "/org/freedesktop/portal/desktop/forge",
            screencast_iface,
        )?
        .build()?;

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

struct ScreenshotPortalIface {
    sender: Sender<PortalEvent>,
}

#[zbus::interface(name = "org.freedesktop.impl.portal.Screenshot")]
impl ScreenshotPortalIface {
    fn screenshot(
        &self,
        _handle: zbus::zvariant::ObjectPath<'_>,
        _app_id: &str,
        _parent_window: &str,
        _options: HashMap<String, zbus::zvariant::OwnedValue>,
    ) -> (u32, HashMap<String, zbus::zvariant::OwnedValue>) {
        let _ = self.sender.send(PortalEvent::ScreenshotRequest);

        let ts = chrono_timestamp();
        let path = screenshot_path(&ts);
        let uri = format!("file://{path}");

        let mut results = HashMap::new();
        results.insert(
            "uri".to_string(),
            zbus::zvariant::OwnedValue::from(zbus::zvariant::Str::from(uri)),
        );

        (0, results) // 0 = success
    }

    fn pick_color(
        &self,
        _handle: zbus::zvariant::ObjectPath<'_>,
        _app_id: &str,
        _parent_window: &str,
        _options: HashMap<String, zbus::zvariant::OwnedValue>,
    ) -> (u32, HashMap<String, zbus::zvariant::OwnedValue>) {
        // Take a screenshot and sample center pixel as the picked color
        let ts = chrono_timestamp();
        let path = screenshot_path(&ts);

        let result = std::process::Command::new("grim")
            .arg(&path)
            .output();

        if result.is_ok() {
            // Read the center pixel
            if let Ok(img) = image::open(&path) {
                let rgba = img.into_rgba8();
                let (w, h) = rgba.dimensions();
                let px = rgba.get_pixel(w / 2, h / 2);
                let r = px[0] as f64 / 255.0;
                let g = px[1] as f64 / 255.0;
                let b = px[2] as f64 / 255.0;

                // Clean up temp screenshot
                let _ = std::fs::remove_file(&path);

                let mut results = HashMap::new();
                results.insert(
                    "color".to_string(),
                    zbus::zvariant::OwnedValue::from(zbus::zvariant::Str::from(
                        format!("({r},{g},{b})"),
                    )),
                );
                return (0, results);
            }
            let _ = std::fs::remove_file(&path);
        }

        (1, HashMap::new()) // 1 = cancelled
    }
}

struct ScreenCastPortalIface {
    sender: Sender<PortalEvent>,
}

#[zbus::interface(name = "org.freedesktop.impl.portal.ScreenCast")]
impl ScreenCastPortalIface {
    fn create_session(
        &self,
        _handle: zbus::zvariant::ObjectPath<'_>,
        _session_handle: zbus::zvariant::ObjectPath<'_>,
        _app_id: &str,
        _options: HashMap<String, zbus::zvariant::OwnedValue>,
    ) -> (u32, HashMap<String, zbus::zvariant::OwnedValue>) {
        info!("ScreenCast: create_session");
        let mut results = HashMap::new();
        results.insert(
            "session_id".to_string(),
            zbus::zvariant::OwnedValue::from(zbus::zvariant::Str::from("forge-screencast-0")),
        );
        (0, results)
    }

    fn select_sources(
        &self,
        _handle: zbus::zvariant::ObjectPath<'_>,
        _session_handle: zbus::zvariant::ObjectPath<'_>,
        _app_id: &str,
        _options: HashMap<String, zbus::zvariant::OwnedValue>,
    ) -> (u32, HashMap<String, zbus::zvariant::OwnedValue>) {
        info!("ScreenCast: select_sources");
        (0, HashMap::new())
    }

    fn start(
        &self,
        _handle: zbus::zvariant::ObjectPath<'_>,
        _session_handle: zbus::zvariant::ObjectPath<'_>,
        _app_id: &str,
        _parent_window: &str,
        _options: HashMap<String, zbus::zvariant::OwnedValue>,
    ) -> (u32, HashMap<String, zbus::zvariant::OwnedValue>) {
        let _ = self.sender.send(PortalEvent::ScreencastStart);
        info!("ScreenCast: start");

        // Return streams info — clients will use PipeWire for actual data.
        // The PipeWire node ID would be provided here in a full implementation.
        let mut results = HashMap::new();
        results.insert(
            "streams".to_string(),
            zbus::zvariant::OwnedValue::from(zbus::zvariant::Str::from("[]")),
        );
        (0, results)
    }

    #[zbus(property)]
    fn available_source_types(&self) -> u32 {
        // 1 = monitor, 2 = window, 4 = virtual
        1 // Only monitor capture supported
    }

    #[zbus(property)]
    fn available_cursor_modes(&self) -> u32 {
        // 1 = hidden, 2 = embedded, 4 = metadata
        1 | 2
    }

    #[zbus(property)]
    fn version(&self) -> u32 {
        4
    }
}
