use std::sync::Arc;
use zbus::blocking::fdo::DBusProxy;
use zbus::blocking::Connection;

use crate::error::{Error, Result};
use crate::traits::MediaPlayer;
use crate::types::{Metadata, PlaybackStatus};

const MPRIS_PREFIX: &str = "org.mpris.MediaPlayer2.";
const MPRIS_PATH: &str = "/org/mpris/MediaPlayer2";

#[zbus::proxy(
    interface = "org.mpris.MediaPlayer2.Player",
    default_path = "/org/mpris/MediaPlayer2"
)]
trait MprisPlayer {
    fn play(&self) -> zbus::Result<()>;
    fn pause(&self) -> zbus::Result<()>;
    fn play_pause(&self) -> zbus::Result<()>;
    fn stop(&self) -> zbus::Result<()>;
    fn next(&self) -> zbus::Result<()>;
    fn previous(&self) -> zbus::Result<()>;

    #[zbus(property)]
    fn playback_status(&self) -> zbus::Result<String>;

    #[zbus(property)]
    fn metadata(
        &self,
    ) -> zbus::Result<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>;
}

pub struct Player {
    name: String,
    bus_name: String,
    connection: Arc<Connection>,
}

impl Player {
    pub fn new(connection: Arc<Connection>, bus_name: String) -> Result<Self> {
        let name = bus_name
            .strip_prefix(MPRIS_PREFIX)
            .unwrap_or(&bus_name)
            .to_string();

        Ok(Self {
            name,
            bus_name,
            connection,
        })
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn bus_name(&self) -> &str {
        &self.bus_name
    }

    fn proxy(&self) -> Result<MprisPlayerProxyBlocking<'_>> {
        let proxy = MprisPlayerProxyBlocking::builder(&self.connection)
            .destination(self.bus_name.as_str())?
            .path(MPRIS_PATH)?
            .build()?;
        Ok(proxy)
    }
}

impl MediaPlayer for Player {
    fn play(&self) -> Result<()> {
        self.proxy()?.play()?;
        Ok(())
    }

    fn pause(&self) -> Result<()> {
        self.proxy()?.pause()?;
        Ok(())
    }

    fn play_pause(&self) -> Result<()> {
        self.proxy()?.play_pause()?;
        Ok(())
    }

    fn stop(&self) -> Result<()> {
        self.proxy()?.stop()?;
        Ok(())
    }

    fn next(&self) -> Result<()> {
        self.proxy()?.next()?;
        Ok(())
    }

    fn previous(&self) -> Result<()> {
        self.proxy()?.previous()?;
        Ok(())
    }

    fn status(&self) -> Result<PlaybackStatus> {
        let status_str = self.proxy()?.playback_status()?;
        status_str.parse()
    }

    fn metadata(&self) -> Result<Metadata> {
        let map = self.proxy()?.metadata()?;
        Ok(Metadata::from_mpris(map))
    }
}

pub fn list_players() -> Result<Vec<Player>> {
    let connection = Arc::new(Connection::session()?);
    let proxy = DBusProxy::new(&connection)?;
    let names = proxy.list_names()?;

    let players: Vec<Player> = names
        .into_iter()
        .filter(|name| name.starts_with(MPRIS_PREFIX))
        .map(|bus_name| Player::new(Arc::clone(&connection), bus_name.to_string()))
        .collect::<Result<Vec<_>>>()?;

    if players.is_empty() {
        return Err(Error::NoPlayersFound);
    }
    Ok(players)
}
