use anyhow::Context;
use std::fmt::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;
use std::{fs, thread};

#[derive(Clone, Copy, Debug)]
struct Rgb {
    r: u8,
    g: u8,
    b: u8,
}

impl Rgb {
    fn hex(self) -> String {
        format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
    }

    fn luminance(self) -> f32 {
        0.2126f32.mul_add(f32::from(self.r), 0.7152 * f32::from(self.g))
    }

    fn saturation_proxy(self) -> u8 {
        let max = self.r.max(self.g).max(self.b);
        let min = self.r.min(self.g).min(self.b);
        max - min
    }
}

fn pick_roles(colors: &[Rgb]) -> (Rgb, Rgb, Rgb, Rgb, Rgb) {
    let mut sorted = colors.to_vec();
    sorted.sort_by(|a, b| a.luminance().total_cmp(&b.luminance()));
    let bg = sorted[0];
    let fg = *sorted.last().unwrap_or(&Rgb {
        r: 225,
        g: 225,
        b: 225,
    });

    let accent = sorted
        .iter()
        .copied()
        .max_by_key(|c| c.saturation_proxy())
        .unwrap_or(fg);

    let warn = sorted.get(2).copied().unwrap_or(accent);
    let ok = sorted.get(4).copied().unwrap_or(accent);

    (bg, fg, accent, warn, ok)
}

/// Helper to write files atomically (write to temp -> rename).
/// This prevents Waybar/terminals from reading empty files during the write.
fn atomic_write(path: &Path, contents: &str) -> anyhow::Result<()> {
    let dir = path.parent().context("path has no parent")?;
    fs::create_dir_all(dir)?;

    // Create a unique temp file in the same directory
    let tmp_path = dir.join(format!(
        ".{}.tmp.{}",
        path.file_name().unwrap().to_string_lossy(),
        std::process::id()
    ));

    fs::write(&tmp_path, contents)?;
    fs::rename(&tmp_path, path)?; // Atomic rename
    Ok(())
}

/// Helper to reload waybar
fn reload_waybar_only() {
    let _ = Command::new("pkill")
        .args(["-USR2", "-x", "waybar"])
        .status();
}

// fn reload_or_start_waybar() -> anyhow::Result<()> {
//     // 1) Reload if running (Waybar supports SIGUSR2 reload)
//     let reloaded = Command::new("pkill")
//         .args(["-USR2", "-x", "waybar"])
//         .status()
//         .map(|s| s.success())
//         .unwrap_or(false);

//     if reloaded {
//         log::info!("Reloaded waybar (SIGUSR2)");
//         return Ok(());
//     }

//     // 2) Not running (or pkill missing/failed). Start ONLY if not already running.
//     // This avoids duplicates even if the previous check failed for some reason.
//     let status = Command::new("sh")
//         .args(["-c", "pgrep -x waybar >/dev/null || (waybar & disown)"])
//         .status()
//         .context("failed to run waybar start guard")?;

//     if status.success() {
//         log::info!("Ensured waybar is running");
//         Ok(())
//     } else {
//         anyhow::bail!("Failed to ensure waybar is running (guard command failed)");
//     }
// }

pub fn write_waybar_css(
    theme_dir: &Path,
    palette: &[color_thief::Color],
) -> anyhow::Result<PathBuf> {
    let colors: Vec<Rgb> = palette
        .iter()
        .map(|c| Rgb {
            r: c.r,
            g: c.g,
            b: c.b,
        })
        .collect();

    let (bg, fg, accent, warn, ok) = pick_roles(&colors);

    let mut css = String::new();
    let _ = writeln!(css, "/* auto-generated by randpaper */");
    let _ = writeln!(css, "@define-color rp_bg {};", bg.hex());
    let _ = writeln!(css, "@define-color rp_fg {};", fg.hex());
    let _ = writeln!(css, "@define-color rp_accent {};", accent.hex());
    let _ = writeln!(css, "@define-color rp_warn {};", warn.hex());
    let _ = writeln!(css, "@define-color rp_ok {};", ok.hex());
    let _ = writeln!(css, "@define-color rp_border {};", accent.hex());
    let _ = writeln!(css, "@define-color rp_muted {};", bg.hex());

    let out = theme_dir.join("waybar.css");
    atomic_write(&out, &css)?;
    Ok(out)
}

/// Ensures the Waybar theme file exists with a default palette.
/// Call this once at startup to prevent Waybar from crashing on @import.
pub fn ensure_theme_exists() -> anyhow::Result<()> {
    let theme_dir = dirs::config_dir()
        .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
        .join("randpaper/themes");

    let waybar_css = theme_dir.join("waybar.css");

    // If the file already exists, we're done
    if waybar_css.exists() {
        return Ok(());
    }

    // Create directory and write a default theme
    fs::create_dir_all(&theme_dir)?;

    let default_css = r"/* Default randpaper theme - will be replaced on first wallpaper change */
@define-color rp_bg #1e1e2e;
@define-color rp_fg #cdd6f4;
@define-color rp_accent #89b4fa;
@define-color rp_warn #f9e2af;
@define-color rp_ok #a6e3a1;
@define-color rp_border #89b4fa;
@define-color rp_muted #313244;
";

    atomic_write(&waybar_css, default_css)?;
    log::info!("Created default theme file at {}", waybar_css.display());

    Ok(())
}

pub fn update_theme_file(image_path: &Path) -> anyhow::Result<()> {
    log::info!("updating theme for image: {}", image_path.display());

    let img = image::open(image_path).context("Failed to open image for theming")?;
    let img = img.resize(300, 300, image::imageops::FilterType::Nearest);
    let buffer = img.to_rgb8();

    let palette = color_thief::get_palette(buffer.as_raw(), color_thief::ColorFormat::Rgb, 10, 16)
        .map_err(|e| anyhow::anyhow!("Color thief error: {e:?}"))?;

    let colors: Vec<Rgb> = palette
        .iter()
        .map(|c| Rgb {
            r: c.r,
            g: c.g,
            b: c.b,
        })
        .collect();
    let (bg, fg, _accent, _warn, _ok) = pick_roles(&colors);

    let theme_dir = dirs::config_dir()
        .ok_or_else(|| anyhow::anyhow!("Could not find config directory"))?
        .join("randpaper/themes");

    // 1. Write Waybar CSS (Atomic)
    let _ = write_waybar_css(&theme_dir, &palette)?;

    // 2. Write Terminal Configs (Atomic)
    let mut ghostty = String::from("# Auto-generated by randpaper\n");
    let mut kitty = String::from("# Auto-generated by randpaper\n");
    let mut foot = String::from("# Auto-generated by randpaper\n[colors]\n");

    for (i, color) in palette.iter().enumerate() {
        let hex = format!("{:02x}{:02x}{:02x}", color.r, color.g, color.b);

        let _ = writeln!(ghostty, "palette = {i}=#{hex}");
        let _ = writeln!(kitty, "color{i} #{hex}");

        if i < 8 {
            let _ = writeln!(foot, "regular{i}={hex}");
        } else {
            let _ = writeln!(foot, "bright{}={hex}", i - 8);
        }
    }

    let _ = writeln!(ghostty, "background = {}", bg.hex());
    let _ = writeln!(ghostty, "foreground = {}", fg.hex());
    let _ = writeln!(kitty, "background {}", bg.hex());
    let _ = writeln!(kitty, "foreground {}", fg.hex());
    let _ = writeln!(foot, "background={:02x}{:02x}{:02x}", bg.r, bg.g, bg.b);
    let _ = writeln!(foot, "foreground={:02x}{:02x}{:02x}", fg.r, fg.g, fg.b);

    atomic_write(&theme_dir.join("ghostty.config"), &ghostty)?;
    atomic_write(&theme_dir.join("kitty.conf"), &kitty)?;
    atomic_write(&theme_dir.join("foot.ini"), &foot)?;

    log::info!("Updated themes in {}", theme_dir.display());

    // Small delay to ensure filesystem has flushed the writes
    thread::sleep(Duration::from_millis(100));

    // Reload or start Waybar
    // let _ = reload_or_start_waybar();
    () = reload_waybar_only();

    // Best-effort reload terminals (don't auto-start if not running)
    // let _ = Command::new("pkill").args(["-USR2", "-x", "foot"]).status();
    let foot_result = Command::new("sh")
        .args(["-c", "pkill -USR2 foot; sleep 0.05; pkill -USR2 foot"])
        .status();
    log::info!("Foot reload result: {foot_result:?}");

    // Reload Kitty (explicit socket if needed)
    let _ = Command::new("kitten").args(["@", "load-config"]).status();

    let _ = Command::new("pkill")
        .args(["-USR2", "-x", "ghostty"])
        .status();

    Ok(())
}
