use std::{
    fmt::Display,
    sync::{LazyLock, Mutex, 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::{artist::Artist, track::lyric_data::LyricData};

use crate::{
    player::{PlayerCommand, PlayerRequest, PlayerTx},
    playlist::{LoopMode, ShuffleMode, TracklistTrack},
};

pub enum MprisEvent {
    CurrentlyPlayingChanged {
        currently_playing: Option<TracklistTrack>,
    },
    IsPlayingChanged {
        is_playing: bool,
        changed_at: Option<f64>,
    },
    LoopStatusChanged {
        loop_mode: LoopMode,
    },
    ShuffleStatusChanged {
        shuffle_mode: ShuffleMode,
    },
    VolumeChanged {
        volume: f32,
    },
    Seeked {
        time: f64,
    },
    Shutdown,
}

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::LoopStatusChanged { .. } => f.write_str("LoopStatusChanged"),
            MprisEvent::ShuffleStatusChanged { .. } => f.write_str("ShuffleStatusChanged"),
            MprisEvent::VolumeChanged { .. } => f.write_str("VolumeChanged"),
            MprisEvent::Seeked { .. } => f.write_str("Seeked"),
            MprisEvent::Shutdown => f.write_str("Shutdown"),
        }
    }
}

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

#[derive(Debug)]
pub struct MprisConnector {
    player_tx: Sender<PlayerRequest>,
    state: Mutex<MprisState>,
}

#[derive(Debug)]
struct MprisState {
    currently_playing: Option<TracklistTrack>,
    is_playing: bool,

    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,
            is_playing: false,
        }
    }
}

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
        }
    }

    pub fn open(player_tx: Sender<PlayerRequest>) -> tokio::sync::mpsc::Sender<MprisEvent> {
        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();

                if let Err(err) = MprisConnector::run(rx, server).await {
                    error!("MPRIS thread failed with error: {err}")
                };
            })
        });

        tx
    }

    async fn run(
        mut event_rx: tokio::sync::mpsc::Receiver<MprisEvent>,
        server: Server<MprisConnector>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        loop {
            let event = event_rx.recv().await;

            if let Some(event) = &event {
                trace!("MPRIS received event: {event}")
            }

            match event {
                Some(MprisEvent::Shutdown) => {
                    warn!("MPRIS thread is shutting down: Player sent shutdown command");
                    return Ok(());
                }
                Some(MprisEvent::CurrentlyPlayingChanged { currently_playing }) => {
                    let playback_status = if currently_playing.is_none() {
                        PlaybackStatus::Stopped
                    } else if server.imp().state().is_playing {
                        PlaybackStatus::Playing
                    } else {
                        PlaybackStatus::Paused
                    };

                    let metadata = if let Some(currently_playing) = &currently_playing {
                        metadata_from_tracklist_track(currently_playing)?
                    } else {
                        Metadata::builder().trackid(ConnectorId::NO_TRACK).build()
                    };

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

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

                    {
                        let mut state = server.imp().state();
                        state.is_playing = is_playing;
                        state.playback_status = playback_status;
                        state.position_us = (changed_at.unwrap_or(0.0) * 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}")
                    };
                }
                Some(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}")
                    };
                }
                Some(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}")
                    };
                }
                Some(MprisEvent::VolumeChanged { volume }) => {
                    {
                        let mut state = server.imp().state();
                        state.volume = volume as f64
                    }

                    if let Err(err) = server
                        .properties_changed([Property::Volume(volume as f64)])
                        .await
                    {
                        error!("MPRIS Error: {err}")
                    };
                }
                Some(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}")
                    }
                }
                None => {}
            }
        }
    }
}

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

    async fn quit(&self) -> zbus::fdo::Result<()> {
        self.player_tx.quit();
        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", "selene"].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 PlayerInterface for MprisConnector {
    async fn next(&self) -> zbus::fdo::Result<()> {
        self.player_tx.request_mpris(PlayerCommand::TracklistSeek {
            index: 1,
            increment: true,
        });

        Ok(())
    }

    async fn previous(&self) -> zbus::fdo::Result<()> {
        self.player_tx.request_mpris(PlayerCommand::TracklistSeek {
            index: -1,
            increment: true,
        });

        Ok(())
    }

    async fn pause(&self) -> zbus::fdo::Result<()> {
        self.player_tx
            .request_mpris(PlayerCommand::SetPlaying(false));

        Ok(())
    }

    async fn play_pause(&self) -> zbus::fdo::Result<()> {
        self.player_tx.request_mpris(PlayerCommand::TogglePlaying);

        Ok(())
    }

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

    async fn play(&self) -> zbus::fdo::Result<()> {
        self.player_tx
            .request_mpris(PlayerCommand::SetPlaying(true));

        Ok(())
    }

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

        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 {
                self.player_tx.request_mpris(PlayerCommand::Seek {
                    seconds: position.as_micros() as f64 / 1_000_000.0,
                    increment: false,
                });
            }
        }

        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
            .request_mpris(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
            .request_mpris(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.request_mpris(PlayerCommand::SetVolume {
            volume: volume as f32,
            increment: false,
        });

        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_tracklist_track(
    track: &TracklistTrack,
) -> Result<Metadata, Box<dyn std::error::Error>> {
    let mut metadata = Metadata::new();

    let playable = &track.playable;
    let track_meta = track.playable.metadata();
    let container = track.playable.container().unwrap();

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

    if let Some(album) = track_meta.album()? {
        let track_ref = album
            .track_refs()
            .iter()
            .find(|t| t.id == playable.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().artists()?.iter().map(Artist::name)));
        metadata.set_album(Some(album.name));
    };

    let artists = track_meta.artists()?;
    metadata.set_artist(Some(artists.iter().map(Artist::name)));

    metadata.set_lyrics(track_meta.lyric_data.as_ref().map(LyricData::to_string));

    if let Some(cover_art) = &track_meta.cover_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 let Some(genre) = &track_meta.genre {
        metadata.set_genre(Some([genre]));
    }

    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)
}
