use std::{
    fmt::Display,
    sync::{LazyLock, Mutex, atomic::Ordering, mpsc::Sender},
    time::Instant,
};

use lunar_lib::{error, trace, warn};
use mpris_server::{
    LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface,
    Server, Signal, Time, TrackId as ConnectorId, Volume,
    zbus::{self},
};
use selene_core::library::{image_art::CacheableArt, track::lyric_data::LyricData};

use crate::{
    LoopMode, SHUTDOWN, ShuffleMode,
    player::{PlayerCommand, PlayerError, PlayerRequest, PlayerTx},
    playlist::PlayingTrack,
};

pub(crate) enum MprisEvent {
    CurrentlyPlayingChanged { currently_playing: PlayingTrack },
    IsPlayingChanged { is_playing: bool, changed_at: f64 },
    PlaybackStopped,
    LoopStatusChanged { loop_mode: LoopMode },
    ShuffleStatusChanged { shuffle_mode: ShuffleMode },
    VolumeChanged { volume: f32 },
    Seeked { time: f64 },
}

impl Display for MprisEvent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            MprisEvent::CurrentlyPlayingChanged { .. } => f.write_str("CurrentlyPlayingChanged"),
            MprisEvent::IsPlayingChanged { .. } => f.write_str("IsPlayingChanged"),
            MprisEvent::PlaybackStopped => f.write_str("PlaybackStopped"),
            MprisEvent::LoopStatusChanged { .. } => f.write_str("LoopStatusChanged"),
            MprisEvent::ShuffleStatusChanged { .. } => f.write_str("ShuffleStatusChanged"),
            MprisEvent::VolumeChanged { .. } => f.write_str("VolumeChanged"),
            MprisEvent::Seeked { .. } => f.write_str("Seeked"),
        }
    }
}

static META_NO_TRACK: LazyLock<Metadata> =
    LazyLock::new(|| Metadata::builder().trackid(ConnectorId::NO_TRACK).build());

pub struct MprisConnector {
    player_tx: Sender<PlayerRequest>,
    state: Mutex<MprisState>,
}

struct MprisState {
    currently_playing: Option<PlayingTrack>,
    playback_status: PlaybackStatus,
    loop_status: LoopStatus,
    shuffle: bool,
    metadata: Metadata,
    volume: f64,

    position_us: i64,
    recorded_at: Option<Instant>,
}

impl Default for MprisState {
    fn default() -> Self {
        Self {
            playback_status: PlaybackStatus::Stopped,
            loop_status: LoopStatus::None,
            shuffle: false,
            metadata: META_NO_TRACK.clone(),
            volume: 1.0,
            position_us: 0,
            recorded_at: None,
            currently_playing: None,
        }
    }
}

impl MprisConnector {
    fn new(player_tx: Sender<PlayerRequest>) -> Self {
        Self {
            player_tx,
            state: Mutex::new(MprisState::default()),
        }
    }

    fn state(&self) -> std::sync::MutexGuard<'_, MprisState> {
        self.state.lock().unwrap()
    }

    fn position(&self) -> Time {
        let state = self.state.lock().unwrap();

        if let Some(instant) = state.recorded_at {
            Time::from_micros(instant.elapsed().as_micros() as i64 + state.position_us)
        } else {
            Time::ZERO
        }
    }

    #[must_use]
    pub fn open(player_tx: Sender<PlayerRequest>) -> tokio::sync::mpsc::Sender<MprisEvent> {
        trace!("Opening MPRIS thread");
        let (tx, rx) = tokio::sync::mpsc::channel(24);

        std::thread::spawn(move || {
            let rt = tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap();

            rt.block_on(async {
                let server = Server::new(
                    "org.mpris.MediaPlayer2.selene",
                    MprisConnector::new(player_tx),
                )
                .await
                .unwrap();

                match MprisConnector::run(rx, server).await {
                    Ok(()) => {
                        warn!("MPRIS thread exited");
                    }
                    Err(err) => {
                        error!("MPRIS thread failed with error: {err}");
                    }
                }
            });

            SHUTDOWN.store(true, Ordering::Relaxed);
        });

        tx
    }

    async fn run(
        mut event_rx: tokio::sync::mpsc::Receiver<MprisEvent>,
        server: Server<MprisConnector>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        while let Some(event) = event_rx.recv().await {
            if SHUTDOWN.load(Ordering::Relaxed) {
                return Ok(());
            }

            match event {
                MprisEvent::CurrentlyPlayingChanged { currently_playing } => {
                    let metadata = metadata_from_playing(&currently_playing)?;

                    {
                        let mut state = server.imp().state();
                        state.currently_playing = Some(currently_playing);
                        state.metadata = metadata.clone();
                        state.position_us = 0;
                        state.recorded_at = Some(Instant::now());
                    }

                    if let Err(err) = server
                        .properties_changed([Property::Metadata(metadata)])
                        .await
                    {
                        error!("MPRIS Error: {err}");
                    }
                }
                MprisEvent::PlaybackStopped => {
                    let metadata = META_NO_TRACK.clone();
                    {
                        let mut state = server.imp().state();
                        state.currently_playing = None;
                        state.playback_status = PlaybackStatus::Stopped;
                        state.metadata = metadata.clone();
                        state.position_us = 0;
                        state.recorded_at = None;
                    }

                    if let Err(err) = server
                        .properties_changed([
                            Property::Metadata(metadata),
                            Property::PlaybackStatus(PlaybackStatus::Stopped),
                        ])
                        .await
                    {
                        error!("MPRIS Error: {err}");
                    }
                }
                MprisEvent::IsPlayingChanged {
                    is_playing,
                    changed_at,
                } => {
                    let playback_status = if is_playing {
                        PlaybackStatus::Playing
                    } else {
                        PlaybackStatus::Paused
                    };

                    {
                        let mut state = server.imp().state();
                        state.playback_status = playback_status;
                        state.position_us = (changed_at * 1_000_000.0) as i64;
                        state.recorded_at = Some(Instant::now());
                    }

                    if let Err(err) = server
                        .properties_changed([Property::PlaybackStatus(playback_status)])
                        .await
                    {
                        error!("MPRIS Error: {err}");
                    }
                }
                MprisEvent::LoopStatusChanged { loop_mode: status } => {
                    let status = match status {
                        LoopMode::None => LoopStatus::None,
                        LoopMode::Loop => LoopStatus::Playlist,
                        LoopMode::LoopAndReshuffle => LoopStatus::Playlist,
                        LoopMode::RepeatTrack => LoopStatus::Track,
                    };

                    {
                        let mut state = server.imp().state();
                        state.loop_status = status;
                    }

                    if let Err(err) = server
                        .properties_changed([Property::LoopStatus(status)])
                        .await
                    {
                        error!("MPRIS Error: {err}");
                    }
                }
                MprisEvent::ShuffleStatusChanged {
                    shuffle_mode: status,
                } => {
                    let status = !matches!(status, ShuffleMode::None);

                    {
                        let mut state = server.imp().state();
                        state.shuffle = status;
                    }

                    if let Err(err) = server.properties_changed([Property::Shuffle(status)]).await {
                        error!("MPRIS Error: {err}");
                    }
                }
                MprisEvent::VolumeChanged { volume } => {
                    {
                        let mut state = server.imp().state();
                        state.volume = f64::from(volume);
                    }

                    if let Err(err) = server
                        .properties_changed([Property::Volume(f64::from(volume))])
                        .await
                    {
                        error!("MPRIS Error: {err}");
                    }
                }
                MprisEvent::Seeked { time } => {
                    let micros = (time * 1_000_000.0) as i64;
                    let time = Time::from_micros(micros);

                    {
                        let mut state = server.imp().state();
                        state.position_us = micros;
                        state.recorded_at = Some(Instant::now());
                    }

                    if let Err(err) = server.emit(Signal::Seeked { position: time }).await {
                        error!("MPRIS Error: {err}");
                    }
                }
            }
        }
        Ok(())
    }
}

impl RootInterface for MprisConnector {
    async fn raise(&self) -> zbus::fdo::Result<()> {
        Ok(())
    }

    async fn quit(&self) -> zbus::fdo::Result<()> {
        SHUTDOWN.store(true, Ordering::Relaxed);
        Ok(())
    }

    async fn can_quit(&self) -> zbus::fdo::Result<bool> {
        Ok(true)
    }

    async fn fullscreen(&self) -> zbus::fdo::Result<bool> {
        Err(zbus::fdo::Error::ZBus(zbus::Error::Unsupported))
    }

    async fn set_fullscreen(&self, _fullscreen: bool) -> zbus::Result<()> {
        Err(zbus::Error::Unsupported)
    }

    async fn can_set_fullscreen(&self) -> zbus::fdo::Result<bool> {
        Ok(false)
    }

    async fn can_raise(&self) -> zbus::fdo::Result<bool> {
        Ok(false)
    }

    async fn has_track_list(&self) -> zbus::fdo::Result<bool> {
        Ok(false)
    }

    async fn identity(&self) -> zbus::fdo::Result<String> {
        Ok("Selene".to_owned())
    }

    async fn desktop_entry(&self) -> zbus::fdo::Result<String> {
        Err(zbus::fdo::Error::ZBus(zbus::Error::Unsupported))
    }

    async fn supported_uri_schemes(&self) -> zbus::fdo::Result<Vec<String>> {
        Ok(["file"].into_iter().map(str::to_owned).collect())
    }

    async fn supported_mime_types(&self) -> zbus::fdo::Result<Vec<String>> {
        Ok([
            "audio/flac",
            "audio/ogg",
            "audio/opus",
            "audio/wav",
            "audio/mpeg",
        ]
        .into_iter()
        .map(str::to_owned)
        .collect())
    }
}

impl From<PlayerError> for mpris_server::zbus::fdo::Error {
    fn from(value: PlayerError) -> Self {
        Self::Failed(value.to_string())
    }
}

impl From<PlayerError> for mpris_server::zbus::Error {
    fn from(value: PlayerError) -> Self {
        Self::Failure(value.to_string())
    }
}

impl PlayerInterface for MprisConnector {
    async fn next(&self) -> zbus::fdo::Result<()> {
        self.player_tx.command(PlayerCommand::Next)?;
        Ok(())
    }

    async fn previous(&self) -> zbus::fdo::Result<()> {
        self.player_tx.command(PlayerCommand::Previous)?;
        Ok(())
    }

    async fn pause(&self) -> zbus::fdo::Result<()> {
        self.player_tx
            .command(PlayerCommand::SetIsPlaying { is_playing: false })?;
        Ok(())
    }

    async fn play_pause(&self) -> zbus::fdo::Result<()> {
        self.player_tx
            .command(PlayerCommand::TogglePlaying { callback: None })?;
        Ok(())
    }

    async fn stop(&self) -> zbus::fdo::Result<()> {
        self.player_tx.command(PlayerCommand::Stop)?;
        Ok(())
    }

    async fn play(&self) -> zbus::fdo::Result<()> {
        self.player_tx
            .command(PlayerCommand::SetIsPlaying { is_playing: true })?;
        Ok(())
    }

    async fn seek(&self, offset: Time) -> zbus::fdo::Result<()> {
        let seconds = offset.as_micros() as f64 / 1_000_000.0;
        self.player_tx.command(PlayerCommand::Seek {
            seconds,
            increment: true,
            callback: None,
        })?;
        Ok(())
    }

    async fn set_position(&self, track_id: ConnectorId, position: Time) -> zbus::fdo::Result<()> {
        let state = self.state.lock().unwrap();

        if let Some(tracklist_track) = &state.currently_playing {
            let id = tracklist_track.mpris_id();

            if id == track_id {
                let seconds = position.as_micros() as f64 / 1_000_000.0;
                self.player_tx.command(PlayerCommand::Seek {
                    seconds,
                    increment: false,
                    callback: None,
                })?;
            }
        }

        Ok(())
    }

    async fn open_uri(&self, _uri: String) -> zbus::fdo::Result<()> {
        // TODO: Allow URI opening for selene:// and file://
        Err(zbus::fdo::Error::ZBus(zbus::Error::Unsupported))
    }

    async fn playback_status(&self) -> zbus::fdo::Result<PlaybackStatus> {
        Ok(self.state.lock().unwrap().playback_status)
    }

    async fn loop_status(&self) -> zbus::fdo::Result<LoopStatus> {
        Ok(self.state.lock().unwrap().loop_status)
    }

    async fn set_loop_status(&self, loop_status: LoopStatus) -> zbus::Result<()> {
        let loop_mode = match loop_status {
            LoopStatus::None => LoopMode::None,
            LoopStatus::Playlist => LoopMode::Loop,
            LoopStatus::Track => LoopMode::RepeatTrack,
        };

        self.player_tx
            .command(PlayerCommand::PlaylistSetLoopMode { loop_mode })?;

        Ok(())
    }

    async fn rate(&self) -> zbus::fdo::Result<PlaybackRate> {
        Ok(1.0)
    }

    async fn set_rate(&self, _rate: PlaybackRate) -> zbus::Result<()> {
        Err(zbus::Error::Unsupported)
    }

    async fn shuffle(&self) -> zbus::fdo::Result<bool> {
        Ok(self.state.lock().unwrap().shuffle)
    }

    async fn set_shuffle(&self, shuffle: bool) -> zbus::Result<()> {
        let shuffle_mode = if shuffle {
            ShuffleMode::Full
        } else {
            ShuffleMode::None
        };

        self.player_tx
            .command(PlayerCommand::PlaylistSetShuffleMode { shuffle_mode })?;

        Ok(())
    }

    async fn metadata(&self) -> zbus::fdo::Result<Metadata> {
        Ok(self.state.lock().unwrap().metadata.clone())
    }

    async fn volume(&self) -> zbus::fdo::Result<Volume> {
        Ok(self.state.lock().unwrap().volume)
    }

    async fn set_volume(&self, volume: Volume) -> zbus::Result<()> {
        self.player_tx.command(PlayerCommand::SetVolume {
            volume: volume as f32,
            increment: false,
            callback: None,
        })?;

        Ok(())
    }

    async fn position(&self) -> zbus::fdo::Result<Time> {
        Ok(self.position())
    }

    async fn minimum_rate(&self) -> zbus::fdo::Result<PlaybackRate> {
        Ok(1.0)
    }

    async fn maximum_rate(&self) -> zbus::fdo::Result<PlaybackRate> {
        Ok(1.0)
    }

    async fn can_go_next(&self) -> zbus::fdo::Result<bool> {
        Ok(true)
    }

    async fn can_go_previous(&self) -> zbus::fdo::Result<bool> {
        Ok(true)
    }

    async fn can_play(&self) -> zbus::fdo::Result<bool> {
        Ok(true)
    }

    async fn can_pause(&self) -> zbus::fdo::Result<bool> {
        Ok(true)
    }

    async fn can_seek(&self) -> zbus::fdo::Result<bool> {
        Ok(true)
    }

    async fn can_control(&self) -> zbus::fdo::Result<bool> {
        Ok(true)
    }
}

fn metadata_from_playing(playing: &PlayingTrack) -> Result<Metadata, Box<dyn std::error::Error>> {
    let mut metadata = Metadata::new();

    let track = &playing.source;
    let artists = track.artists();

    let track_meta = track.metadata();
    let container = track.container();

    let url = format!("file://{}", container.path().to_str().unwrap());
    metadata.set_url(Some(url));
    metadata.set_trackid(Some(playing.mpris_id()));

    if let Some((album, album_artists, ..)) = playing.source.album_info() {
        let track_ref = album
            .track_refs()
            .iter()
            .find(|t| t.id == track.id())
            .unwrap();

        metadata.set_disc_number(track_ref.disc_num.map(|v| v as i32));
        metadata.set_track_number(track_ref.track_num.map(|v| v as i32));
        metadata.set_album_artist(Some(album_artists.iter().map(|a| a.name())));
        metadata.set_album(Some(&album.name));
    }

    metadata.set_artist(Some(artists.iter().map(|a| a.name())));

    if let Some(and_then) = track_meta
        .lyric_data
        .as_ref()
        .and_then(LyricData::to_lyrics)
    {
        metadata.set_lyrics(Some(and_then));
    }

    if let Some(cover_art) = &track_meta.art {
        let cover_file = cover_art.cache_file(512, 512)?;
        let url = format!("file://{}", cover_file.to_str().unwrap());
        metadata.set_art_url(Some(url));
    }

    if !track_meta.genre.is_empty() {
        metadata.set_genre(Some(track_meta.genre.clone()));
    }

    metadata.set_title(Some(track_meta.safe_title()));

    let length = Time::from_micros((container.stream().duration() * 1_000_000.0) as i64);
    metadata.set_length(Some(length));

    for (key, value) in &track_meta.other {
        metadata.set(&format!("xesam:{key}"), Some(value.to_owned()));
    }

    Ok(metadata)
}
