use std::{
    collections::{HashSet, VecDeque},
    sync::{Arc, atomic::AtomicBool, mpsc::Sender},
};

use lunar_lib::database::{DatabaseEntry, EntryIdExt, TransactionError};
use rand::{rngs::StdRng, seq::SliceRandom};
use selene_core::{
    database::{LibraryDb, Resolveable},
    library::{
        album::Album,
        artist::ArtistId,
        collectable::Collectable,
        collection::{Collection, CollectionId},
        track::{ResolvedTrack, Track},
    },
};
use serde::{Deserialize, Serialize};

use crate::{LoopMode, ShuffleMode, changed_state, event_handler::EventTx, player::PlayerEvent};

mod playlist_rng;
pub use playlist_rng::*;

mod core_impls;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PlayingTrack {
    pub source: ResolvedTrack,
    pub position: Option<usize>,
}

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

        if let Some(position) = self.position {
            ObjectPath::from_string_unchecked(format!(
                "/org/mpris/MediaPlayer2/TrackList/{position}",
            ))
            .into()
        } else {
            ObjectPath::from_string_unchecked("/org/mpris/MediaPlayer2/TrackList/queued_track".to_string())
            .into()
        }
    }

    fn new(
        track: Arc<Track>,
        position: Option<usize>,
        db: &LibraryDb,
    ) -> Result<Self, TransactionError> {
        let resolved = Track::resolve(track, db)?;
        Ok(Self {
            source: resolved,
            position,
        })
    }

    fn from_queue(track: Arc<Track>, db: &LibraryDb) -> Result<Self, TransactionError> {
        Self::new(track, None, db)
    }

    fn from_tracklist(
        track: Arc<Track>,
        id: usize,
        db: &LibraryDb,
    ) -> Result<Self, TransactionError> {
        Self::new(track, Some(id), db)
    }
}

pub struct Playlist {
    event_tx: Sender<PlayerEvent>,
    rng: PlaylistRng,

    queue: VecDeque<Arc<Track>>,
    playlist: Vec<Playable>,

    tracklist: Vec<Arc<Track>>,
    tracklist_index: Option<usize>,

    shuffle_mode: ShuffleMode,
    loop_mode: LoopMode,
    looping: Arc<AtomicBool>,
}

impl Playlist {
    /// Clears the playlist and the tracklist
    pub fn clear(&mut self) {
        self.playlist.clear();
        self.tracklist.clear();
        self.tracklist_index = None;
        self.event_tx.event(PlayerEvent::TracklistChanged {
            tracklist: Vec::new(),
            position: self.tracklist_index,
        });
    }

    // Rebuilds the tracklist with new rng, moving whatever was currently playing to the front
    fn rebuild_tracklist(&mut self) {
        self.rng = PlaylistRng::new();
        let mut rng = self.rng.current_rng();
        let mut tracklist = self.build_tracklist(&mut rng);

        if let Some(current) = self.tracklist_index.and_then(|i| self.tracklist.get(i))
            && let Some(new_pos) = tracklist.iter().position(|t| Arc::ptr_eq(t, current))
        {
            tracklist.swap(0, new_pos);
            self.tracklist_index = Some(0);
        } else {
            self.tracklist_index = None;
        }

        self.tracklist = tracklist;
        self.event_tx.event(PlayerEvent::TracklistChanged {
            tracklist: self.tracklist.iter().map(|t| t.id()).collect(),
            position: self.tracklist_index,
        });
        changed_state();
    }

    fn shuffle_tracklist(&mut self, rng: &mut StdRng) {
        self.tracklist = self.build_tracklist(rng);
        self.event_tx.event(PlayerEvent::TracklistChanged {
            tracklist: self.tracklist.iter().map(|t| t.id()).collect(),
            position: self.tracklist_index,
        });
        changed_state();
    }

    fn build_tracklist(&self, rng: &mut StdRng) -> Vec<Arc<Track>> {
        let mut tracklist: Vec<Arc<Track>> = self
            .playlist
            .iter()
            .flat_map(|p| p.flatten_shuffle(self.shuffle_mode, rng))
            .collect();

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

        tracklist
    }
}

#[derive(Debug, Clone)]
pub struct PlayableAlbum {
    album: Arc<Album>,
    tracks: Vec<Arc<Track>>,
}
impl PlayableAlbum {
    fn from_album(album: Arc<Album>, db: &LibraryDb) -> Result<PlayableAlbum, TransactionError> {
        let tracks = album.tracks_cache(db)?;
        Ok(Self { album, tracks })
    }
}

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

impl Playable {
    #[must_use]
    pub fn flatten(&self) -> Vec<Arc<Track>> {
        let mut buf = Vec::new();
        let mut stack = vec![self];

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

        buf
    }

    #[must_use]
    pub fn flatten_shuffle(&self, shuffle_mode: ShuffleMode, rng: &mut StdRng) -> Vec<Arc<Track>> {
        let mut buf: Vec<Arc<Track>> = Vec::new();
        let mut stack = vec![self];

        while let Some(last) = stack.pop() {
            match last {
                Playable::Track { track } => buf.push(track.clone()),
                Playable::Album { album, .. } => {
                    let mut album = album.tracks.iter().map(Arc::clone).collect::<Vec<_>>();
                    if matches!(
                        shuffle_mode,
                        ShuffleMode::TracksOnly | ShuffleMode::CollectionsAndTracks
                    ) {
                        album.shuffle(rng);
                    }

                    buf.extend(album);
                }
                Playable::Artist { tracks, albums, .. } => {
                    let mut items: Vec<_> = albums
                        .iter()
                        .map(|a| a.tracks.iter().map(Arc::clone).collect::<Vec<_>>())
                        .chain(tracks.iter().map(|t| vec![t.clone()]))
                        .collect();

                    if matches!(
                        shuffle_mode,
                        ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
                    ) {
                        items.shuffle(rng);
                    }

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

                        tracks
                    }));
                }
                Playable::Collection { playables, .. } => {
                    let mut playables = playables.iter().collect::<Vec<_>>();

                    if matches!(
                        shuffle_mode,
                        ShuffleMode::Full | ShuffleMode::CollectionsAndTracks
                    ) {
                        playables.shuffle(rng);
                    }

                    stack.extend(playables.into_iter().rev());
                }
            }
        }

        buf
    }

    pub fn from_collectable(
        collectable: Collectable,
        db: &LibraryDb,
    ) -> Result<Playable, TransactionError> {
        let playable = match collectable {
            Collectable::Track(track_id) => {
                let track = track_id
                    .cache_get(db)?
                    .ok_or(TransactionError::MissingEntry)?;
                Playable::Track { track }
            }
            Collectable::Artist(artist_id) => {
                let artist = artist_id
                    .cache_get(db)?
                    .ok_or(TransactionError::MissingEntry)?;

                let tracks = artist.all_tracks_cache(db)?.into_iter().collect();

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

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

                Playable::Album {
                    album: PlayableAlbum::from_album(album, db)?,
                }
            }
            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, db)?.ok_or(TransactionError::MissingEntry)?;
                let mut frames = vec![Frame {
                    collection_id,
                    remaining: root.collectables(db)?.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) => {
                                assert!(
                                    !frame.ancestors.contains(&inner_id),
                                    "Invalid collection: Cyclical reference"
                                );

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

                                let inner = inner_id
                                    .cache_get(db)?
                                    .ok_or(TransactionError::MissingEntry)?;

                                frames.push(Frame {
                                    collection_id: inner_id,
                                    remaining: inner.collectables(db)?.into_iter(),
                                    playables: Vec::new(),
                                    ancestors: child_ancestors,
                                });
                            }
                            other => {
                                frame.playables.push(Playable::from_collectable(other, db)?);
                            }
                        }
                    } 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)
    }

    #[must_use]
    pub fn to_collectable(&self) -> Collectable {
        match self {
            Playable::Track { track } => Collectable::Track(track.id()),
            Playable::Album { album } => Collectable::Album(album.album.id()),
            Playable::Artist { artist, .. } => Collectable::Artist(*artist),
            Playable::Collection { collection, .. } => Collectable::Collection(*collection),
        }
    }

    fn id_as_bytes(&self) -> [u8; 32] {
        match self {
            Playable::Track { track } => *track.id(),
            Playable::Album { album, .. } => *album.album.id(),
            Playable::Artist { artist, .. } => **artist,
            Playable::Collection { collection, .. } => **collection,
        }
    }
}
