use std::{
    collections::{HashSet, VecDeque},
    fmt::Display,
};

use blake3::Hash;
use rand::{rng, seq::SliceRandom};
use selene_core::{
    database::{DatabaseEntry, DatabaseError},
    library::{
        album::Album,
        artist::{Artist, ArtistId},
        collection::{Collectable, Collection, CollectionId},
        track::{Track, TrackId, track_meta::TrackMeta},
    },
    media_container::MediaContainer,
};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TracklistTrack {
    pub playable: Box<PlayableTrack>,
    pub id: usize,
}

impl TracklistTrack {
    pub fn can_play(&self) -> bool {
        self.playable.container.is_some()
    }

    #[cfg(feature = "mpris")]
    pub fn mpris_id(&self) -> mpris_server::TrackId {
        use mpris_server::zbus::zvariant::ObjectPath;

        ObjectPath::from_string_unchecked(format!("/org/mpris/MediaPlayer2/TrackList/{}", self.id))
            .into()
    }
}

#[derive(Debug)]
pub struct Playlist {
    pub queue: VecDeque<PlayableTrack>,
    playlist: Vec<Playable>,
    tracklist: Vec<TracklistTrack>,
    tracklist_index: Option<usize>,

    pub shuffle_mode: ShuffleMode,
    pub loop_mode: LoopMode,
}

impl Default for Playlist {
    fn default() -> Self {
        Self::new()
    }
}

impl Playlist {
    pub fn new() -> Self {
        Self {
            queue: VecDeque::new(),
            playlist: Vec::new(),
            tracklist: Vec::new(),
            tracklist_index: None,

            shuffle_mode: ShuffleMode::None,
            loop_mode: LoopMode::None,
        }
    }

    /// Clears the playlist and the tracklist
    pub fn clear(&mut self) {
        self.playlist.clear();
        self.tracklist.clear();
        self.tracklist_index = None;
    }

    /// Peeks at the next item
    ///
    /// If this function returns none, it means the end of the track list has been reached, and the next consumption will follow the [`LoopMode`]
    pub fn peek_next(&self) -> Option<&TracklistTrack> {
        if let Some(tracklist_index) = self.tracklist_index {
            self.tracklist.get(tracklist_index + 1)
        } else {
            self.tracklist.first()
        }
    }

    /// Returns the item at the current index
    pub fn current(&mut self) -> Option<&TracklistTrack> {
        let i = self.tracklist_index?;
        self.tracklist.get(i)
    }

    /// Moves to the next item and returns it
    ///
    /// If this function returns none, it means there is nothing left to play or nothing to play
    ///
    /// This function will skip tracks without a container.
    pub fn pop_next(&mut self) -> Option<&TracklistTrack> {
        if self.tracklist.is_empty() {
            return None;
        }

        match self.loop_mode {
            LoopMode::None => {
                let start = self.tracklist_index.map(|i| i + 1).unwrap_or(0);

                for i in start..self.tracklist.len() {
                    if self.tracklist[i].can_play() {
                        self.tracklist_index = Some(i);
                        return Some(&self.tracklist[i]);
                    }
                }

                None
            }
            LoopMode::Loop => {
                let start = self.tracklist_index.map(|i| i + 1).unwrap_or(0);

                for n in 0..self.tracklist.len() {
                    let i = (start + n) % self.tracklist.len();

                    if self.tracklist[i].can_play() {
                        self.tracklist_index = Some(i);
                        return Some(&self.tracklist[i]);
                    }
                }

                None
            }
            LoopMode::LoopAndReshuffle => {
                let start = self.tracklist_index.map(|i| i + 1).unwrap_or(0);

                for n in 0..self.tracklist.len() {
                    let i = (start + n) % self.tracklist.len();

                    if i == 0 && n != 0 {
                        self.rebuild_tracklist();
                    }

                    if self.tracklist[i].can_play() {
                        self.tracklist_index = Some(i);
                        return Some(&self.tracklist[i]);
                    }
                }

                None
            }
            LoopMode::RepeatTrack => {
                let i = self.tracklist_index?;
                self.tracklist[i].can_play().then_some(&self.tracklist[i])
            }
        }
    }

    /// Rebuilds the interior tracklist
    pub fn rebuild_tracklist(&mut self) {
        let mut index_map: Vec<usize> = (0..self.playlist.len()).collect();
        if matches!(
            self.shuffle_mode,
            ShuffleMode::CollectionsOnly | ShuffleMode::CollectionsAndTracks
        ) {
            index_map.shuffle(&mut rng());
        }

        let mut tracklist: Vec<_> = index_map
            .into_iter()
            .flat_map(|i| self.playlist[i].clone().flatten_shuffle(self.shuffle_mode))
            .collect();

        if matches!(self.shuffle_mode, ShuffleMode::Full) {
            tracklist.shuffle(&mut rng());
        }

        self.tracklist_index = None;
        self.tracklist = tracklist
            .into_iter()
            .enumerate()
            .map(|(i, t)| TracklistTrack {
                playable: Box::new(t),
                id: i,
            })
            .collect();
    }

    /// If the looping mode is set to `Loop`, this function will modulo the input
    ///
    /// If the looping mode is set to `LoopAndReshuffle`, this function will modulo the input and reshuffle the tracklist
    pub fn tracklist_seek(&mut self, to: isize, increment: bool) -> usize {
        let to = if increment {
            self.tracklist_index.unwrap_or(0) as isize + to
        } else {
            to
        };

        let len = self.tracklist.len() as isize;

        match self.loop_mode {
            LoopMode::None | LoopMode::RepeatTrack => {
                self.tracklist_index = Some(to.clamp(0, len) as usize);
            }
            LoopMode::Loop => {
                self.tracklist_index = Some(to.rem_euclid(len) as usize);
            }
            LoopMode::LoopAndReshuffle => {
                if to >= len && !matches!(self.shuffle_mode, ShuffleMode::None) {
                    self.rebuild_tracklist();
                }
                self.tracklist_index = Some(to.rem_euclid(len) as usize);
            }
        }
        self.tracklist_index.unwrap()
    }

    /// Gets the current state of the playlist
    pub fn playlist(&self) -> &[Playable] {
        &self.playlist
    }

    /// Sets the current state of the playlist
    pub fn set_playlist(&mut self, playlist: impl IntoIterator<Item = Playable>) {
        self.playlist = playlist.into_iter().collect();
        self.rebuild_tracklist();
    }

    pub fn queue_extend(&mut self, with: impl IntoIterator<Item = Playable>) {
        self.queue.extend(
            with.into_iter()
                .flat_map(|p| p.flatten_shuffle(ShuffleMode::None)),
        );
    }

    pub fn tracklist(&self) -> &[TracklistTrack] {
        &self.tracklist
    }

    // Returns the hash of the queue
    pub fn queue_hash(&self) -> Hash {
        let mut hash_seed = Vec::with_capacity(self.queue.len() * 32);
        for playable in &self.queue {
            hash_seed.extend(playable.track.as_bytes());
        }
        blake3::hash(&hash_seed)
    }

    // Returns the hash of the playlist
    pub fn playlist_hash(&self) -> Hash {
        let mut hash_seed = Vec::with_capacity(self.playlist.len() * 32);
        for playable in &self.playlist {
            hash_seed.extend(playable.hash().as_bytes());
        }
        blake3::hash(&hash_seed)
    }
}

impl Extend<Playable> for Playlist {
    fn extend<T: IntoIterator<Item = Playable>>(&mut self, iter: T) {
        let mut vec: Vec<_> = iter.into_iter().collect();
        if matches!(
            self.shuffle_mode,
            ShuffleMode::CollectionsOnly | ShuffleMode::CollectionsAndTracks
        ) {
            vec.shuffle(&mut rng());
        }

        self.tracklist.extend(
            vec.iter()
                .cloned()
                .flat_map(|p| p.flatten_shuffle(self.shuffle_mode))
                .enumerate()
                .map(|(i, p)| TracklistTrack {
                    playable: Box::new(p),
                    id: i,
                }),
        );
        self.playlist.extend(vec);
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PlayableTrack {
    container: Option<MediaContainer>,
    metadata: TrackMeta,
    track: TrackId,
}

impl PartialEq for PlayableTrack {
    fn eq(&self, other: &Self) -> bool {
        self.track == other.track
    }
}

impl Eq for PlayableTrack {}

impl PlayableTrack {
    pub fn container(&self) -> Option<&MediaContainer> {
        self.container.as_ref()
    }

    pub fn metadata(&self) -> &TrackMeta {
        &self.metadata
    }

    pub fn id(&self) -> TrackId {
        self.track
    }

    pub fn from_track(track: &Track, fallback: bool) -> Self {
        Self {
            container: if fallback {
                Some(
                    track
                        .lib_container()
                        .unwrap_or(track.src_container())
                        .clone(),
                )
            } else {
                track.lib_container().cloned()
            },
            metadata: track.metadata.clone(),
            track: track.id(),
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PlayableAlbum {
    album: Album,
    tracks: Vec<PlayableTrack>,
}
impl PlayableAlbum {
    fn from_album(album: Album) -> Result<PlayableAlbum, DatabaseError> {
        let tracks = album
            .tracks()?
            .iter()
            .map(|t| PlayableTrack::from_track(t, false))
            .collect();

        Ok(Self { album, tracks })
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Playable {
    Track {
        track: Box<PlayableTrack>,
    },
    Album {
        album: Box<PlayableAlbum>,
    },
    Artist {
        artist: ArtistId,
        tracks: Vec<PlayableTrack>,
        albums: Vec<PlayableAlbum>,
    },
    Collection {
        collection: CollectionId,
        playables: Vec<Playable>,
    },
}

impl Playable {
    pub fn flatten_shuffle(self, shuffle_mode: ShuffleMode) -> Vec<PlayableTrack> {
        let mut buf = Vec::new();
        let mut stack = vec![self];

        while let Some(last) = stack.pop() {
            match last {
                Playable::Track { track } => buf.push(*track),
                Playable::Album { mut album, .. } => {
                    if matches!(
                        shuffle_mode,
                        ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
                    ) {
                        album.tracks.shuffle(&mut rng());
                    }

                    buf.extend(album.tracks)
                }
                Playable::Artist {
                    mut tracks,
                    mut albums,
                    ..
                } => {
                    if matches!(
                        shuffle_mode,
                        ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
                    ) {
                        albums.shuffle(&mut rng());
                    }

                    buf.extend(albums.into_iter().flat_map(|mut a| {
                        if matches!(
                            shuffle_mode,
                            ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
                        ) {
                            a.tracks.shuffle(&mut rng());
                        }

                        a.tracks
                    }));

                    if matches!(
                        shuffle_mode,
                        ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
                    ) {
                        tracks.shuffle(&mut rng());
                    }

                    buf.extend(tracks);
                }
                Playable::Collection { mut playables, .. } => {
                    if matches!(
                        shuffle_mode,
                        ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
                    ) {
                        playables.shuffle(&mut rng());
                    }

                    stack.extend(playables);
                }
            }
        }

        if matches!(shuffle_mode, ShuffleMode::Full) {
            buf.shuffle(&mut rng());
        }

        buf
    }

    pub fn flatten(self) -> Vec<PlayableTrack> {
        let mut buf = Vec::new();
        let mut stack = vec![self];

        while let Some(last) = stack.pop() {
            match last {
                Playable::Track { track } => buf.push(*track),
                Playable::Album { album } => buf.extend(album.tracks),
                Playable::Artist { tracks, albums, .. } => {
                    buf.extend(albums.into_iter().flat_map(|a| a.tracks));
                    buf.extend(tracks)
                }
                Playable::Collection { playables, .. } => stack.extend(playables),
            }
        }

        buf
    }

    pub fn from_collectable(collectable: Collectable) -> Result<Playable, DatabaseError> {
        let playable = match collectable {
            Collectable::Track(track_id) => {
                let track = Track::db_get(track_id)?.ok_or(DatabaseError::MissingEntry)?;
                Playable::Track {
                    track: Box::new(PlayableTrack::from_track(&track, false)),
                }
            }
            Collectable::Artist(artist_id) => {
                let artist = Artist::db_get(artist_id)?.ok_or(DatabaseError::MissingEntry)?;

                let tracks = artist
                    .all_tracks()?
                    .iter()
                    .map(|t| PlayableTrack::from_track(t, false))
                    .collect();

                let albums: Vec<PlayableAlbum> = artist
                    .albums()?
                    .into_iter()
                    .map(PlayableAlbum::from_album)
                    .collect::<Result<_, _>>()?;

                Playable::Artist {
                    artist: artist_id,
                    tracks,
                    albums,
                }
            }
            Collectable::Album(album_id) => {
                let album = Album::db_get(album_id)?.ok_or(DatabaseError::MissingEntry)?;

                Playable::Album {
                    album: Box::new(PlayableAlbum::from_album(album)?),
                }
            }
            Collectable::Collection(collection_id) => {
                struct Frame<I: Iterator<Item = Collectable>> {
                    collection_id: CollectionId,
                    remaining: I,
                    playables: Vec<Playable>,
                    ancestors: HashSet<CollectionId>,
                }

                let root = Collection::db_get(collection_id)?.ok_or(DatabaseError::MissingEntry)?;
                let mut frames = vec![Frame {
                    collection_id,
                    remaining: root.collectables.into_iter(),
                    playables: Vec::new(),
                    ancestors: HashSet::from([collection_id]),
                }];

                loop {
                    let frame = frames.last_mut().unwrap();
                    if let Some(item) = frame.remaining.next() {
                        match item {
                            Collectable::Collection(inner_id) => {
                                if frame.ancestors.contains(&inner_id) {
                                    panic!("Invalid collection: Cyclical reference")
                                }

                                let mut child_ancestors = frame.ancestors.clone();
                                child_ancestors.insert(inner_id);

                                let inner = Collection::db_get(inner_id)?
                                    .ok_or(DatabaseError::MissingEntry)?;
                                frames.push(Frame {
                                    collection_id: inner_id,
                                    remaining: inner.collectables.into_iter(),
                                    playables: Vec::new(),
                                    ancestors: child_ancestors,
                                });
                            }
                            other => {
                                frame.playables.push(Playable::from_collectable(other)?);
                            }
                        }
                    } else {
                        let completed = frames.pop().unwrap();
                        let result = Playable::Collection {
                            collection: completed.collection_id,
                            playables: completed.playables,
                        };
                        match frames.last_mut() {
                            Some(parent) => parent.playables.push(result),
                            None => return Ok(result),
                        }
                    }
                }
            }
        };
        Ok(playable)
    }

    fn hash(&self) -> Hash {
        match self {
            Playable::Track { track } => *track.track,
            Playable::Album { album, .. } => *album.album.id(),
            Playable::Artist { artist, .. } => **artist,
            Playable::Collection { collection, .. } => **collection,
        }
    }

    pub fn display(&self) -> Result<String, DatabaseError> {
        match self {
            Playable::Track { track } => Ok(format!("{} (Track)", track.metadata.safe_title())),
            Playable::Album { album, .. } => Ok(format!("{} (Album)", album.album.name())),
            Playable::Artist { artist, .. } => {
                let artist = Artist::db_get(*artist)?.ok_or(DatabaseError::MissingEntry)?;
                Ok(format!("{} (Artist)", artist.name()))
            }
            Playable::Collection { collection, .. } => {
                let collection =
                    Collection::db_get(*collection)?.ok_or(DatabaseError::MissingEntry)?;
                Ok(format!("{} (Collection)", collection.name))
            }
        }
    }
}

#[cfg(feature = "clap")]
use clap::ValueEnum;

#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(feature = "clap", derive(ValueEnum))]
pub enum ShuffleMode {
    /// Tracks are not shuffled, they are played as is
    None,

    /// Only collections are shuffled, meaning the collections are shuffled, but not the contents
    CollectionsOnly,

    /// Randomize tracks only, meaning the contents of collections are shuffled, but not the collections themselves
    TracksOnly,

    /// Randomize collection order and the tracks inside the collection
    CollectionsAndTracks,

    /// Randomizes the order of everything
    Full,
}

impl Display for ShuffleMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ShuffleMode::None => f.write_str("None"),
            ShuffleMode::CollectionsOnly => f.write_str("RandomizeCollectionsOnly"),
            ShuffleMode::TracksOnly => f.write_str("RandomizeTracksOnly"),
            ShuffleMode::CollectionsAndTracks => f.write_str("RandomizeCollectionsAndTracks"),
            ShuffleMode::Full => f.write_str("RandomizeFull"),
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(feature = "clap", derive(ValueEnum))]
pub enum LoopMode {
    /// Disables looping
    None,

    /// When the end of the tracklist is reached, loop back to the beginning
    Loop,

    /// When the end of the tracklist is reached, reshuffle the tracklist using the shuffle mode and loop
    LoopAndReshuffle,
    RepeatTrack,
}

impl Display for LoopMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LoopMode::None => f.write_str("None"),
            LoopMode::Loop => f.write_str("Loop"),
            LoopMode::LoopAndReshuffle => f.write_str("LoopAndReshuffle"),
            LoopMode::RepeatTrack => f.write_str("RepeatTrack"),
        }
    }
}
