use std::{
    fmt::{Debug, Display},
    sync::{atomic::Ordering, mpsc::Sender},
};

use blake3::Hash;
use selene_core::{
    database::LibraryDb,
    library::{collectable::Collectable, track::TrackId},
};
use serde::{Deserialize, Serialize};

use crate::{
    LoopMode, ShuffleMode,
    config::daemon_config,
    decoder::{DecoderEvent, DecoderTx},
    player::{PlaybackStatus, Player, PlayerError, PlayerQueryFlags, QueryResult},
    playlist::{Playable, PlayingTrack},
};

#[derive(Debug, Clone)]
pub(crate) enum PlayerCommand {
    // Generic
    Flush {
        callback: Sender<()>,
    },

    // Playback
    Play {
        playables: Vec<Playable>,
    },
    Next,
    Previous,
    SetIsPlaying {
        is_playing: bool,
    },
    TogglePlaying {
        callback: Option<Sender<PlaybackStatus>>,
    },
    Stop,
    SetVolume {
        volume: f32,
        increment: bool,
        callback: Option<Sender<f32>>,
    },
    Seek {
        seconds: f64,
        increment: bool,
        callback: Option<Sender<Option<f64>>>,
    },

    // The override queue
    QueueExtend {
        with: Vec<Playable>,
    },
    QueueSet {
        to: Vec<Playable>,
        expected_state: Hash,
        callback: Option<Sender<bool>>,
    },
    QueueShuffle,
    QueueClear,

    // The playlist
    PlaylistExtend {
        items: Vec<Playable>,
    },
    PlaylistSet {
        playables: Vec<Playable>,
        expected_state: Hash,
        callback: Option<Sender<bool>>,
    },
    PlaylistClear,
    PlaylistSetShuffleMode {
        shuffle_mode: ShuffleMode,
    },
    PlaylistSetLoopMode {
        loop_mode: LoopMode,
    },

    // Tracklist
    TracklistSeek {
        index: isize,
        increment: bool,
        callback: Option<Sender<Option<usize>>>,
    },

    // Queries
    GetState {
        flags: PlayerQueryFlags,
        callback: Sender<QueryResult>,
    },
}

impl Display for PlayerCommand {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PlayerCommand::Flush { .. } => f.write_str("Flush"),
            PlayerCommand::Play { .. } => f.write_str("Play"),
            PlayerCommand::Next => f.write_str("Next"),
            PlayerCommand::Previous => f.write_str("Previous"),
            PlayerCommand::SetIsPlaying { .. } => f.write_str("SetIsPlaying"),
            PlayerCommand::TogglePlaying { .. } => f.write_str("TogglePlaying"),
            PlayerCommand::Stop => f.write_str("Stop"),
            PlayerCommand::SetVolume { .. } => f.write_str("SetVolume"),
            PlayerCommand::Seek { .. } => f.write_str("Seek"),
            PlayerCommand::QueueExtend { .. } => f.write_str("QueueExtend"),
            PlayerCommand::QueueSet { .. } => f.write_str("QueueSet"),
            PlayerCommand::QueueShuffle => f.write_str("QueueShuffle"),
            PlayerCommand::QueueClear => f.write_str("QueueClear"),
            PlayerCommand::PlaylistExtend { .. } => f.write_str("PlaylistExtend"),
            PlayerCommand::PlaylistSet { .. } => f.write_str("PlaylistSet"),
            PlayerCommand::PlaylistClear => f.write_str("PlaylistClear"),
            PlayerCommand::PlaylistSetShuffleMode { .. } => f.write_str("PlaylistSetShuffleMode"),
            PlayerCommand::PlaylistSetLoopMode { .. } => f.write_str("PlaylistSetLoopMode"),
            PlayerCommand::TracklistSeek { .. } => f.write_str("TracklistSeek"),
            PlayerCommand::GetState { .. } => f.write_str("GetState"),
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[non_exhaustive]
pub enum PlayerEvent {
    CurrentlyPlayingChanged {
        currently_playing: PlayingTrack,
    },
    PlaybackIsPlayingChanged {
        is_playing: bool,
        changed_at: f64,
    },
    PlaybackStopped,

    ShuffleModeChanged {
        shuffle_mode: ShuffleMode,
    },
    LoopModeChanged {
        loop_mode: LoopMode,
    },

    VolumeChanged {
        volume: f32,
    },
    SeekOccured {
        time: f64,
    },

    QueueChanged {
        queue: Vec<TrackId>,
    },
    PlaylistChanged {
        playlist: Vec<Collectable>,
    },
    TracklistChanged {
        tracklist: Vec<TrackId>,
        position: Option<usize>,
    },

    Shutdown,
}

impl Display for PlayerEvent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            PlayerEvent::CurrentlyPlayingChanged { .. } => f.write_str("CurrentlyPlayingChanged"),
            PlayerEvent::PlaybackIsPlayingChanged { .. } => f.write_str("PlaybackIsPlayingChanged"),
            PlayerEvent::PlaybackStopped => f.write_str("PlaybackStopped"),
            PlayerEvent::ShuffleModeChanged { .. } => f.write_str("ShuffleModeChanged"),
            PlayerEvent::LoopModeChanged { .. } => f.write_str("LoopModeChanged"),
            PlayerEvent::VolumeChanged { .. } => f.write_str("VolumeChanged"),
            PlayerEvent::SeekOccured { .. } => f.write_str("SeekOccured"),
            PlayerEvent::QueueChanged { .. } => f.write_str("QueueChanged"),
            PlayerEvent::PlaylistChanged { .. } => f.write_str("PlaylistChanged"),
            PlayerEvent::TracklistChanged { .. } => f.write_str("TracklistChanged"),
            PlayerEvent::Shutdown => f.write_str("Shutdown"),
        }
    }
}

pub(crate) enum PlayerRequest {
    DecoderEvent(DecoderEvent),
    Command(Box<PlayerCommand>),
}

pub(crate) trait PlayerTx {
    fn decoder_event(&self, event: DecoderEvent) -> Result<(), PlayerError>;
    fn command(&self, command: PlayerCommand) -> Result<(), PlayerError>;
}

impl PlayerTx for Sender<PlayerRequest> {
    fn decoder_event(&self, event: DecoderEvent) -> Result<(), PlayerError> {
        self.send(PlayerRequest::DecoderEvent(event))?;
        Ok(())
    }

    fn command(&self, command: PlayerCommand) -> Result<(), PlayerError> {
        self.send(PlayerRequest::Command(Box::new(command)))?;
        Ok(())
    }
}

impl Player {
    pub(crate) fn run_command(
        &mut self,
        command: PlayerCommand,
        db: &LibraryDb,
    ) -> Result<(), PlayerError> {
        match command {
            // Generic
            PlayerCommand::Flush { callback } => {
                let _ = callback.send(());
            }

            // Queries
            PlayerCommand::GetState { flags, callback } => {
                let mut query = QueryResult::default();

                if flags.contains(PlayerQueryFlags::PLAYBACK_STATE) {
                    query.playback_state = Some(self.playback_state.load(Ordering::SeqCst));
                }

                if flags.contains(PlayerQueryFlags::CURRENTLY_PLAYING) {
                    let currently_playing = self.decoder_tx.get_playing()?;

                    if let Some((track, pos)) = currently_playing.map(|c| (c.source, c.position)) {
                        query.currently_playing = Some(Some(track.id()));
                        query.currently_playing_index = Some(pos);
                    } else {
                        query.currently_playing = Some(None);
                        query.currently_playing_index = Some(None);
                    }
                }

                if flags.contains(PlayerQueryFlags::VOLUME) {
                    query.volume = Some(f32::from_bits(self.volume.load(Ordering::SeqCst)));
                }

                if flags.contains(PlayerQueryFlags::TIME) {
                    query.time = Some(self.decoder_tx.get_time()?);
                }

                if flags.contains(PlayerQueryFlags::QUEUE) {
                    query.queue = Some(self.playlist.queue().iter().map(|t| t.id()).collect());
                }

                if flags.contains(PlayerQueryFlags::QUEUE_STATE) {
                    query.queue_state = Some(self.playlist.queue_hash());
                }

                if flags.contains(PlayerQueryFlags::PLAYLIST) {
                    query.playlist = Some(
                        self.playlist
                            .playlist()
                            .iter()
                            .map(Playable::to_collectable)
                            .collect(),
                    );
                }

                if flags.contains(PlayerQueryFlags::TRACKLIST) {
                    query.tracklist =
                        Some(self.playlist.tracklist().iter().map(|t| t.id()).collect());
                }

                if flags.contains(PlayerQueryFlags::TRACKLIST_POSITION) {
                    query.tracklist_position = Some(self.playlist.position());
                }

                if flags.contains(PlayerQueryFlags::PLAYLIST_STATE) {
                    query.playlist_state = Some(self.playlist.playlist_hash());
                }

                if flags.contains(PlayerQueryFlags::SHUFFLE_MODE) {
                    query.shuffle_mode = Some(self.playlist.shuffle_mode());
                }

                if flags.contains(PlayerQueryFlags::LOOP_MODE) {
                    query.loop_mode = Some(self.playlist.loop_mode());
                }

                let _ = callback.send(query);
            }

            // Playback
            PlayerCommand::Play {
                playables: playable,
            } => {
                self.playlist.set_playlist(playable);

                let loaded = self.replace_decoders(true)?;

                if loaded {
                    self.decoder_tx.set_playing(true)?;
                }
            }
            PlayerCommand::SetIsPlaying { is_playing } => {
                self.decoder_tx.set_playing(is_playing)?;
            }
            PlayerCommand::TogglePlaying { callback } => {
                let state = self.decoder_tx.toggle_playing()?;
                if let Some(callback) = callback {
                    let _ = callback.send(state);
                }
            }
            PlayerCommand::Stop => {
                self.decoder_tx.stop()?;
            }
            PlayerCommand::SetVolume {
                volume,
                increment,
                callback,
            } => {
                let vol = self.set_volume(volume, increment);
                if let Some(callback) = callback {
                    let _ = callback.send(vol);
                }
            }
            PlayerCommand::Seek {
                seconds,
                increment,
                callback,
            } => {
                let time = self.decoder_tx.seek(seconds, increment)?;
                if let Some(callback) = callback {
                    let _ = callback.send(time);
                }
            }

            // Queue
            PlayerCommand::QueueSet {
                expected_state,
                to,
                callback,
            } => {
                let equals = expected_state == self.playlist.queue_hash();
                if equals {
                    self.playlist.set_queue(&to);
                    self.try_preload(db)?;
                }
                if let Some(callback) = callback {
                    let _ = callback.send(equals);
                }
            }
            PlayerCommand::QueueExtend { with } => {
                self.playlist.extend_queue(&with);
                self.try_preload(db)?;
            }
            PlayerCommand::QueueShuffle => {
                self.playlist.shuffle_queue();
                self.try_preload(db)?;
            }
            PlayerCommand::QueueClear => {
                self.playlist.clear_queue();
                self.try_preload(db)?;
            }

            // Playlist
            PlayerCommand::PlaylistSet {
                expected_state,
                playables,
                callback,
            } => {
                let equals = expected_state == self.playlist.playlist_hash();
                if equals {
                    self.playlist.set_playlist(playables);
                    self.try_preload(db)?;
                }

                if let Some(callback) = callback {
                    let _ = callback.send(equals);
                }
            }
            PlayerCommand::PlaylistExtend { items } => {
                self.playlist.extend_playlist(items);
                self.try_preload(db)?;
            }
            PlayerCommand::PlaylistClear => {
                self.playlist.clear();
                self.try_preload(db)?;
            }
            PlayerCommand::PlaylistSetShuffleMode { shuffle_mode } => {
                self.playlist.set_shuffle_mode(shuffle_mode);
                self.try_preload(db)?;
            }
            PlayerCommand::PlaylistSetLoopMode { loop_mode } => {
                self.playlist.set_loop_mode(loop_mode);
            }

            // Tracklist
            PlayerCommand::TracklistSeek {
                index,
                increment,
                callback,
            } => {
                let position = self.playlist.tracklist_seek(index, increment);
                self.replace_decoders(false)?;
                if let Some(callback) = callback {
                    let _ = callback.send(position);
                }
            }
            PlayerCommand::Next => {
                if matches!(self.playlist.loop_mode(), LoopMode::RepeatTrack) {
                    self.decoder_tx.seek(0.0, false)?;
                } else {
                    self.decoder_tx.skip()?;
                }
            }
            PlayerCommand::Previous => {
                if matches!(self.playlist.loop_mode(), LoopMode::RepeatTrack) {
                    self.decoder_tx.seek(0.0, false)?;
                } else if let Some(restart_on_previous_thresh) =
                    daemon_config().playback.previous_restart_thresh
                    && let Some(time) = self.decoder_tx.get_time()?
                    && time >= restart_on_previous_thresh
                {
                    self.decoder_tx.seek(0.0, false)?;
                } else {
                    self.playlist.tracklist_seek(-1, true);
                    self.replace_decoders(false)?;
                }
            }
        }
        Ok(())
    }
}
