use std::thread;
use std::time::Duration;

use calloop::channel::{self, Sender};
use calloop::LoopHandle;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::utils::{Logical, Point, Rectangle, Size};
use tracing::{error, info};

use crate::drawing::{ForgeElement, RenderTexture, Texture};
use crate::forge::Forge;
use crate::output::Canvas;
use crate::text::TextRenderer;

const MEDIA_BG_RGBA: [u8; 4] = [35, 35, 35, 230];
const TITLE_COLOR: [u8; 4] = [255, 255, 255, 255];
const ARTIST_COLOR: [u8; 4] = [180, 180, 180, 255];
const CONTROL_COLOR: [u8; 4] = [255, 255, 255, 255];
const CONTROL_HEIGHT: i32 = 80;
const CONTROL_WIDTH: i32 = 300;
const CONTROL_PADDING: i32 = 12;

#[derive(Debug, Clone)]
pub struct MediaInfo {
    pub title: String,
    pub artist: String,
    pub playing: bool,
    pub available: bool,
    /// Cover art URL from MPRIS metadata (mpris:artUrl).
    pub art_url: Option<String>,
}

impl Default for MediaInfo {
    fn default() -> Self {
        Self {
            title: String::new(),
            artist: String::new(),
            playing: false,
            available: false,
            art_url: None,
        }
    }
}

pub struct MprisState {
    pub media: MediaInfo,
    dirty: bool,
    last_scale: f64,

    bg_texture: RenderTexture,
    title_texture: Option<RenderTexture>,
    artist_texture: Option<RenderTexture>,
    controls_texture: Option<RenderTexture>,
    /// Cached cover art texture.
    cover_texture: Option<RenderTexture>,
    /// URL of the cover art that was last loaded (to avoid reloading).
    cover_loaded_url: Option<String>,
}

impl MprisState {
    pub fn new() -> Self {
        Self {
            media: MediaInfo::default(),
            dirty: true,
            last_scale: 0.0,
            bg_texture: RenderTexture::new(Texture::from_color(MEDIA_BG_RGBA, true)),
            title_texture: None,
            artist_texture: None,
            controls_texture: None,
            cover_texture: None,
            cover_loaded_url: None,
        }
    }

    /// Start MPRIS D-Bus listener in a background thread.
    pub fn start_listener(event_loop: &LoopHandle<'static, Forge>) {
        let (sender, channel) = channel::channel::<MediaInfo>();

        event_loop
            .insert_source(channel, |event, _, forge| {
                if let channel::Event::Msg(info) = event {
                    forge.mpris.media = info;
                    forge.mpris.dirty = true;
                    forge.windows.set_dirty();
                    forge.unstall();
                }
            })
            .expect("mpris channel");

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

    /// Send play/pause command to the active MPRIS player.
    pub fn play_pause() {
        let _ = std::process::Command::new("sh")
            .args(["-c", "dbus-send --session --type=method_call \
                   --dest=$(dbus-send --session --print-reply --dest=org.freedesktop.DBus \
                   /org/freedesktop/DBus org.freedesktop.DBus.ListNames 2>/dev/null | \
                   grep -oP 'org.mpris.MediaPlayer2.\\S+' | head -1) \
                   /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause"])
            .spawn();
    }

    /// Send next track command.
    pub fn next() {
        let _ = std::process::Command::new("playerctl").args(["next"]).spawn();
    }

    /// Send previous track command.
    pub fn previous() {
        let _ = std::process::Command::new("playerctl").args(["previous"]).spawn();
    }

    /// Handle tap on media controls. Returns true if handled.
    pub fn handle_tap(&self, canvas: &Canvas, point: Point<f64, Logical>, on_lockscreen: bool) -> bool {
        if !self.media.available {
            return false;
        }

        let bounds = self.control_bounds(canvas, on_lockscreen);
        if !bounds.to_f64().contains(point) {
            return false;
        }

        let relative_x = point.x - bounds.loc.x as f64;
        let third = bounds.size.w as f64 / 3.0;

        if relative_x < third {
            Self::previous();
        } else if relative_x < 2.0 * third {
            Self::play_pause();
        } else {
            Self::next();
        }

        true
    }

    fn control_bounds(&self, canvas: &Canvas, on_lockscreen: bool) -> Rectangle<i32, Logical> {
        let size = canvas.size();
        let w = if canvas.is_mobile() { size.w - 24 } else { CONTROL_WIDTH };
        let x = (size.w - w) / 2;
        let y = if on_lockscreen {
            size.h / 4 + 80
        } else {
            // In quick settings - below sliders and toggles
            size.h - CONTROL_HEIGHT - 20
        };
        Rectangle::new(Point::from((x, y)), Size::from((w, CONTROL_HEIGHT)))
    }

    fn update_textures(
        &mut self,
        renderer: &mut GlesRenderer,
        canvas: &Canvas,
        text_renderer: &mut TextRenderer,
    ) {
        let scale = canvas.scale();
        if !self.dirty && (self.last_scale - scale).abs() < 0.01 {
            return;
        }
        self.dirty = false;
        self.last_scale = scale;

        if !self.media.available {
            self.title_texture = None;
            self.artist_texture = None;
            self.controls_texture = None;
            self.cover_texture = None;
            self.cover_loaded_url = None;
            return;
        }

        // Load cover art if URL changed
        if self.media.art_url != self.cover_loaded_url {
            self.cover_loaded_url = self.media.art_url.clone();
            self.cover_texture = None;
            if let Some(url) = &self.media.art_url {
                self.cover_texture = load_cover_art(renderer, scale, url);
            }
        }

        let title_font = (14.0 * scale as f32).round();
        let artist_font = (12.0 * scale as f32).round();
        let control_font = (18.0 * scale as f32).round();

        // Title
        if !self.media.title.is_empty() {
            let (buf, w, h) = text_renderer.render_line(&self.media.title, title_font, TITLE_COLOR);
            if w > 0 && h > 0 {
                self.title_texture = Some(RenderTexture::new(
                    Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
                ));
            }
        } else {
            self.title_texture = None;
        }

        // Artist
        if !self.media.artist.is_empty() {
            let (buf, w, h) = text_renderer.render_line(&self.media.artist, artist_font, ARTIST_COLOR);
            if w > 0 && h > 0 {
                self.artist_texture = Some(RenderTexture::new(
                    Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
                ));
            }
        } else {
            self.artist_texture = None;
        }

        // Controls text: "<<  |>/||  >>"
        let play_sym = if self.media.playing { "||" } else { ">" };
        let controls = format!("<<    {play_sym}    >>");
        let (buf, w, h) = text_renderer.render_line(&controls, control_font, CONTROL_COLOR);
        if w > 0 && h > 0 {
            self.controls_texture = Some(RenderTexture::new(
                Texture::from_buffer(renderer, scale, &buf, w as i32, h as i32, false),
            ));
        }
    }

    pub fn textures(
        &mut self,
        textures: &mut Vec<ForgeElement>,
        canvas: &Canvas,
        renderer: &mut GlesRenderer,
        text_renderer: &mut TextRenderer,
        on_lockscreen: bool,
    ) {
        if !self.media.available {
            return;
        }

        self.update_textures(renderer, canvas, text_renderer);

        let scale = canvas.scale();
        let bounds = self.control_bounds(canvas, on_lockscreen);
        let physical_bounds = bounds.to_physical_precise_round(scale);

        // Background
        ForgeElement::add_element(
            textures,
            self.bg_texture.clone(),
            physical_bounds.loc,
            physical_bounds,
            None,
            scale,
        );

        // Cover art (left side of control area)
        let text_x_offset = if let Some(cover) = &self.cover_texture {
            let art_size = bounds.size.h - CONTROL_PADDING * 2;
            let art_rect: Rectangle<i32, Logical> = Rectangle::new(
                Point::from((bounds.loc.x + CONTROL_PADDING, bounds.loc.y + CONTROL_PADDING)),
                Size::from((art_size, art_size)),
            );
            let art_phys = art_rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, cover.clone(), art_phys.loc, art_phys, None, scale);
            art_size + CONTROL_PADDING
        } else {
            0
        };

        let mut text_y = bounds.loc.y + CONTROL_PADDING;

        // Title
        if let Some(title) = &self.title_texture {
            let ts = title.size();
            let tx = bounds.loc.x + CONTROL_PADDING + text_x_offset;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((tx, text_y)), ts);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, title.clone(), physical.loc, physical, None, scale);
            text_y += ts.h + 2;
        }

        // Artist
        if let Some(artist) = &self.artist_texture {
            let as_ = artist.size();
            let ax = bounds.loc.x + CONTROL_PADDING + text_x_offset;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((ax, text_y)), as_);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, artist.clone(), physical.loc, physical, None, scale);
        }

        // Controls (centered)
        if let Some(controls) = &self.controls_texture {
            let cs = controls.size();
            let cx = bounds.loc.x + (bounds.size.w - cs.w) / 2;
            let cy = bounds.loc.y + bounds.size.h - cs.h - CONTROL_PADDING;
            let rect: Rectangle<i32, Logical> = Rectangle::new(Point::from((cx, cy)), cs);
            let physical = rect.to_physical_precise_round(scale);
            ForgeElement::add_element(textures, controls.clone(), physical.loc, physical, None, scale);
        }
    }
}

fn run_mpris_listener(sender: Sender<MediaInfo>) -> Result<(), Box<dyn std::error::Error>> {
    info!("MPRIS listener started");

    loop {
        // Poll playerctl for media info
        let info = poll_playerctl();
        let _ = sender.send(info);
        thread::sleep(Duration::from_secs(3));
    }
}

fn poll_playerctl() -> MediaInfo {
    let output = std::process::Command::new("playerctl")
        .args([
            "metadata",
            "--format",
            "{{status}}\n{{title}}\n{{artist}}\n{{album}}\n{{mpris:artUrl}}",
        ])
        .output();

    match output {
        Ok(out) if out.status.success() => {
            let stdout = String::from_utf8_lossy(&out.stdout);
            let lines: Vec<&str> = stdout.lines().collect();
            if lines.len() >= 2 {
                let art_url = lines.get(4).and_then(|s| {
                    let s = s.trim();
                    if s.is_empty() || s == "{{mpris:artUrl}}" {
                        None
                    } else {
                        Some(s.to_string())
                    }
                });
                MediaInfo {
                    playing: lines.first().map_or(false, |s| *s == "Playing"),
                    title: lines.get(1).unwrap_or(&"").to_string(),
                    artist: lines.get(2).unwrap_or(&"").to_string(),
                    available: true,
                    art_url,
                }
            } else {
                MediaInfo::default()
            }
        }
        _ => MediaInfo::default(),
    }
}

/// Load cover art from a file:// URL and create a texture.
fn load_cover_art(renderer: &mut GlesRenderer, scale: f64, url: &str) -> Option<RenderTexture> {
    let path = if let Some(p) = url.strip_prefix("file://") {
        p.to_string()
    } else {
        return None;
    };

    let img = image::open(&path).ok()?.into_rgba8();
    let (iw, ih) = img.dimensions();
    let size = 56u32; // logical cover art size
    let phys_size = (size as f64 * scale) as u32;

    // Resize with nearest-neighbor
    let sx = iw as f64 / phys_size as f64;
    let sy = ih as f64 / phys_size as f64;
    let mut buf = vec![0u8; (phys_size * phys_size * 4) as usize];
    for y in 0..phys_size {
        for x in 0..phys_size {
            let src_x = (x as f64 * sx).min((iw - 1) as f64) as u32;
            let src_y = (y as f64 * sy).min((ih - 1) as f64) as u32;
            let src_off = ((src_y * iw + src_x) * 4) as usize;
            let dst_off = ((y * phys_size + x) * 4) as usize;
            buf[dst_off..dst_off + 4].copy_from_slice(&img.as_raw()[src_off..src_off + 4]);
        }
    }

    let tex = Texture::from_buffer(renderer, scale, &buf, phys_size as i32, phys_size as i32, true);
    Some(RenderTexture::new(tex))
}
