use std::collections::HashMap;

use lunar_lib::database::{DatabaseEntry, DatabaseError, DbHandle};
use thiserror::Error;

use crate::{
    database::LibraryDb,
    library::{
        album::{Album, AlbumId},
        artist::{Artist, ArtistId},
        track::{Track, TrackId},
    },
};

#[derive(Debug, Error)]
pub enum DatabaseReferenceError {
    #[error("Track points to an album, but the album does not point back")]
    TrackDetatchedAlbumRef { track: TrackId, album: AlbumId },

    #[error("Track points to an album, but the album does not exist")]
    TrackDanglingAlbumRef { track: TrackId, album: AlbumId },

    #[error("Track points to an artist, but the artist does not point back")]
    TrackDetatchedArtistRef { track: TrackId, artist: ArtistId },

    #[error("Track points to an artist, but the artist does not exist")]
    TrackDanglingArtistRef { track: TrackId, artist: ArtistId },

    #[error("Album points to a track, but the track does not point back")]
    AlbumDetatchedTrackRef { album: AlbumId, track: TrackId },

    #[error("Album points to a track, but the track does not exist")]
    AlbumDanglingTrackRef { album: AlbumId, track: TrackId },

    #[error("Album points to an artist, but the artist does not point back")]
    AlbumDetatchedArtistRef { album: AlbumId, artist: ArtistId },

    #[error("Album points to an artist, but the artist does not exist")]
    AlbumDanglingArtistRef { album: AlbumId, artist: ArtistId },

    #[error("Artist points to a track, but the track does not exist")]
    ArtistDanglingTrackRef { artist: ArtistId, track: TrackId },

    #[error("Artist points to a track, but the track does not point back")]
    ArtistDetachedTrackRef { artist: ArtistId, track: TrackId },

    #[error("Artist points to an album, but the album does not exist")]
    ArtistDanglingAlbumRef { artist: ArtistId, album: AlbumId },

    #[error("Artist points to an album, but the album does not point back")]
    ArtistDetachedAlbumRef { artist: ArtistId, album: AlbumId },

    #[error("Tried to add track to an artist, but the track is part of the artist's albums")]
    ArtistInvalidTrackRef {
        artist: ArtistId,
        track: TrackId,
        album: AlbumId,
    },
}

pub fn validate() -> Result<Vec<DatabaseReferenceError>, DatabaseError> {
    let mut errors = Vec::new();

    let db = DbHandle::<LibraryDb>::open()?;

    let tracks: HashMap<TrackId, Track> = Track::db_get_all(&db)?
        .into_iter()
        .map(|t| (t.id(), t))
        .collect();

    let albums: HashMap<AlbumId, Album> = Album::db_get_all(&db)?
        .into_iter()
        .map(|a| (a.id(), a))
        .collect();

    let artists: HashMap<ArtistId, Artist> = Artist::db_get_all(&db)?
        .into_iter()
        .map(|a| (a.id(), a))
        .collect();

    for track in tracks.values() {
        if let Some(album_id) = track.metadata.album {
            if let Some(album) = albums.get(&album_id) {
                if !album.track_refs().iter().any(|t| t.id == track.id()) {
                    errors.push(DatabaseReferenceError::TrackDetatchedAlbumRef {
                        track: track.id(),
                        album: album.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::TrackDanglingAlbumRef {
                    track: track.id(),
                    album: album_id,
                });
            }
        }

        for artist_id in &track.metadata.artists {
            if let Some(artist) = artists.get(artist_id) {
                if !artist.tracks.iter().any(|t| *t == track.id()) {
                    if let Some(album_id) = track.metadata.album
                        && artist.albums.contains(&album_id)
                    {
                        continue;
                    }
                    errors.push(DatabaseReferenceError::TrackDetatchedArtistRef {
                        track: track.id(),
                        artist: artist.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::TrackDanglingArtistRef {
                    track: track.id(),
                    artist: *artist_id,
                });
            }
        }
    }

    for album in albums.values() {
        for track in &album.tracks {
            if let Some(track) = tracks.get(&track.id) {
                if !(track.metadata.album == Some(album.id())) {
                    errors.push(DatabaseReferenceError::AlbumDetatchedTrackRef {
                        album: album.id(),
                        track: track.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::AlbumDanglingTrackRef {
                    album: album.id(),
                    track: track.id,
                });
            }
        }

        for artist_id in &album.artists {
            if let Some(artist) = artists.get(artist_id) {
                if !artist.albums.iter().any(|t| *t == album.id()) {
                    errors.push(DatabaseReferenceError::AlbumDetatchedArtistRef {
                        album: album.id(),
                        artist: artist.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::AlbumDanglingArtistRef {
                    album: album.id(),
                    artist: *artist_id,
                });
            }
        }
    }

    for artist in artists.values() {
        for track in &artist.tracks {
            if let Some(track) = tracks.get(track) {
                if !track.metadata.artists.iter().any(|a| *a == artist.id()) {
                    errors.push(DatabaseReferenceError::ArtistDetachedTrackRef {
                        artist: artist.id(),
                        track: track.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::ArtistDanglingTrackRef {
                    artist: artist.id(),
                    track: *track,
                });
            }
        }

        for album in &artist.albums {
            if let Some(album) = albums.get(album) {
                if !album.artists_raw().iter().any(|a| *a == artist.id()) {
                    errors.push(DatabaseReferenceError::ArtistDetachedAlbumRef {
                        artist: artist.id(),
                        album: album.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::ArtistDanglingAlbumRef {
                    artist: artist.id(),
                    album: *album,
                });
            }
        }
    }

    Ok(errors)
}
