use std::collections::HashMap;

use lunar_lib::{
    database::{Db, Id},
    log::{error, trace},
};
use thiserror::Error;

use crate::{
    database::Library,
    library::{album::Album, artist::Artist, track::Track},
};

#[derive(Debug, Error, Hash, PartialEq, Eq)]
pub enum DatabaseReferenceError {
    #[error("Track points to an album, but the album does not point back")]
    TrackDetatchedAlbumRef { track: Id<Track>, album: Id<Album> },

    #[error("Track points to an album, but the album does not exist")]
    TrackDanglingAlbumRef { track: Id<Track>, album: Id<Album> },

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

    #[error("Track points to an artist, but the artist does not exist")]
    TrackDanglingArtistRef {
        track: Id<Track>,
        artist: Id<Artist>,
    },

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

    #[error("Album points to a track, but the track does not exist")]
    AlbumDanglingTrackRef { album: Id<Album>, track: Id<Track> },

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

    #[error("Album points to an artist, but the artist does not exist")]
    AlbumDanglingArtistRef {
        album: Id<Album>,
        artist: Id<Artist>,
    },

    #[error("Artist points to a track, but the track does not exist")]
    ArtistDanglingTrackRef {
        artist: Id<Artist>,
        track: Id<Track>,
    },

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

    #[error("Artist points to an album, but the album does not exist")]
    ArtistDanglingAlbumRef {
        artist: Id<Artist>,
        album: Id<Album>,
    },

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

    #[error("Tried to add track to an artist, but the track is part of the artist's albums")]
    ArtistInvalidTrackRef {
        artist: Id<Artist>,
        track: Id<Track>,
        album: Id<Album>,
    },
}

pub fn validate(db: &Db<Library>) {
    let mut was_error = false;

    let tracks = db
        .iter_entries::<Track>()
        .map(|r| r.map(|e| (e.id(), e)))
        .collect::<Result<HashMap<_, _>, _>>()
        .expect("Failed to open DB during validation");
    let albums = db
        .iter_entries::<Album>()
        .map(|r| r.map(|e| (e.id(), e)))
        .collect::<Result<HashMap<_, _>, _>>()
        .expect("Failed to open DB during validation");
    let artists = db
        .iter_entries::<Artist>()
        .map(|r| r.map(|e| (e.id(), e)))
        .collect::<Result<HashMap<_, _>, _>>()
        .expect("Failed to open DB during validation");

    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()) {
                    trace!(
                        "Track '{tr}' is correctly linked to album '{al}'",
                        tr = track.metadata.safe_title(),
                        al = album.name()
                    );
                } else {
                    error!(
                        "Track '{tr}' points to album '{al}', but {al} does not point back",
                        tr = track.metadata.safe_title(),
                        al = album.name()
                    );
                    was_error = true;
                }
            } else {
                error!(
                    "Track '{tr}' points to album '{al}' which does not exist",
                    tr = track.metadata.safe_title(),
                    al = album_id
                );
                was_error = true;
            }
        }

        for artist_id in &track.metadata.artists {
            if let Some(artist) = artists.get(artist_id) {
                if artist.tracks.iter().any(|t| *t == track.id()) {
                    trace!(
                        "Track '{tr}' is correctly linked to artist '{ar}'",
                        tr = track.metadata.safe_title(),
                        ar = artist.name()
                    );
                } else {
                    error!(
                        "Track '{tr}' points to artist '{ar}', but {ar} does not point back",
                        tr = track.metadata.safe_title(),
                        ar = artist.name()
                    );
                    was_error = true;
                }
            } else {
                error!(
                    "Track '{tr}' points to artist '{ar}' which does not exist",
                    tr = track.metadata.safe_title(),
                    ar = artist_id
                );
                was_error = true;
            }
        }
    }

    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()) {
                    trace!(
                        "Album '{al}' is correctly linked to track '{tr}'",
                        al = album.name(),
                        tr = track.metadata.safe_title()
                    );
                } else {
                    error!(
                        "Album '{al}' points to track '{tr}', but {tr} does not point back",
                        al = album.name(),
                        tr = track.metadata.safe_title()
                    );
                    was_error = true;
                }
            } else {
                error!(
                    "Album '{al}' points to track '{tr}' which does not exist",
                    al = album.name(),
                    tr = track.id
                );
                was_error = true;
            }
        }

        for artist_id in &album.artists {
            if let Some(artist) = artists.get(artist_id) {
                if artist.albums.iter().any(|t| *t == album.id()) {
                    trace!(
                        "Album '{al}' is correctly linked to artist '{ar}'",
                        al = album.name(),
                        ar = artist.name()
                    );
                } else {
                    error!(
                        "Album '{al}' points to artist '{ar}', but {ar} does not point back",
                        al = album.name(),
                        ar = artist.name()
                    );
                    was_error = true;
                }
            } else {
                error!(
                    "Album '{al}' points to artist '{ar}' which does not exist",
                    al = album.name(),
                    ar = artist_id
                );
                was_error = true;
            }
        }
    }

    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()) {
                    trace!(
                        "Artist '{ar}' is correctly linked to track '{tr}'",
                        ar = artist.name(),
                        tr = track.metadata.safe_title()
                    );
                } else {
                    error!(
                        "Artist '{ar}' points to track '{tr}', but {tr} does not point back",
                        ar = artist.name(),
                        tr = track.metadata.safe_title()
                    );
                    was_error = true;
                }
            } else {
                error!(
                    "Artist '{ar}' points to track '{tr}' which does not exist",
                    ar = artist.name(),
                    tr = track
                );
                was_error = true;
            }
        }

        for album in &artist.albums {
            if let Some(album) = albums.get(album) {
                if album.artists().iter().any(|a| *a == artist.id()) {
                    trace!(
                        "Artist '{ar}' is correctly linked to album '{al}'",
                        ar = artist.name(),
                        al = album.name()
                    );
                } else {
                    error!(
                        "Artist '{ar}' points to album '{al}', but {al} does not point back",
                        ar = artist.name(),
                        al = album.name()
                    );
                    was_error = true;
                }
            } else {
                error!(
                    "Artist '{ar}' points to album '{al}' which does not exist",
                    ar = artist.name(),
                    al = album
                );
                was_error = true;
            }
        }
    }

    assert!(!was_error);
}
