using Harmonia.Core.Scanner;
using Microsoft.EntityFrameworkCore;
using SongModel = Harmonia.Core.Models.Song;

namespace Harmonia.Core.Library;

public interface IAudioLibrary
{
    ValueTask AddLocationAsync(string path, CancellationToken cancellationToken);
    void RemoveLocation(string path);
    void RemoveLocations(string[] paths);
    void Scan(string path);
    void Scan(string[] paths);

    event EventHandler<EventArgs>? LocationAdded;
    event EventHandler<EventArgs>? LocationRemoved;
    event EventHandler<EventArgs>? ScanStarted;
    event EventHandler<EventArgs>? ScanFinished;
}

public abstract class AudioLibrary : IAudioLibrary
{
    public event EventHandler<EventArgs>? LocationAdded;
    public event EventHandler<EventArgs>? LocationRemoved;
    public event EventHandler<EventArgs>? ScanStarted;
    public event EventHandler<EventArgs>? ScanFinished;

    protected abstract ValueTask<bool> IncludeLocationAsync(string path, CancellationToken cancellationToken);
    protected abstract ValueTask ScanLocationAsync(string path, CancellationToken cancellationToken);

    public async ValueTask AddLocationAsync(string path, CancellationToken cancellationToken)
    {
        if (await IncludeLocationAsync(path, cancellationToken) == false)
            return;

        LocationAdded?.Invoke(this, new EventArgs());
    }

    public void RemoveLocation(string path)
    {
        RemoveLocations([path]);
    }

    public void RemoveLocations(string[] paths)
    {
        //foreach (string path in paths)
        //{
        //    //if (_locations.Contains(path) == false)
        //    //    continue;


        //}

        LocationRemoved?.Invoke(this, new EventArgs());
    }

    public void Scan(string path)
    {
        Scan([path]);
    }

    public void Scan(string[] paths)
    {
        ScanStarted?.Invoke(this, new EventArgs());

        ScanFinished?.Invoke(this, new EventArgs());
    }
}

public class AudioDatabaseLibrary(IAudioFileScanner audioFileScanner, AudioLibraryContext context) : AudioLibrary
{
    protected override async ValueTask<bool> IncludeLocationAsync(string path, CancellationToken cancellationToken)
    {
        if (CanAddLocation(path) == false)
            return false;

        Location location = GetLocation(path) ?? CreateLocation(path);
        await ScanLocationAsync(location, cancellationToken);

        return true;
    }

    private bool CanAddLocation(string path)
    {
        return context.Folders.Any(folder => folder.Name == path) == false;
    }

    private Location CreateLocation(string path)
    {
        Location location = new()
        {
            Name = path,
            Type = 0
        };

        context.Locations.Add(location);
        context.SaveChanges();

        return location;
    }

    private Location? GetLocation(string path)
    {
        return context.Locations.FirstOrDefault(location => location.Name == path);
    }

    protected override async ValueTask ScanLocationAsync(string path, CancellationToken cancellationToken)
    {
        Location? location = GetLocation(path);

        if (location == null)
            return;

        await ScanLocationAsync(location, cancellationToken);
    }

    private async ValueTask ScanLocationAsync(Location location, CancellationToken cancellationToken)
    {
        SongModel[] songModels = await audioFileScanner.GetSongsFromPathAsync(location.Name, cancellationToken);

        AddFolders(location, songModels);
        AddSongs(songModels);

        // Remove old songs, and clear out folders that no longer hold songs
    }

    private void AddFolders(Location location, SongModel[] songModels)
    {
        string[] folderNames = [.. songModels.Select(songModel => Directory.GetParent(songModel.FileName)?.FullName ?? string.Empty)
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .Distinct()];

        Folder[] folders = [.. context.Folders.Where(folder => folder.Location == location)];
    }

    private void AddSongs(SongModel[] songModels)
    {
        foreach (SongModel songModel in songModels)
        {
            Folder? folder = context.Folders.FirstOrDefault(x => x.Name == songModel.FileDirectory);

            if (folder == null)
                continue;

            Song song = AddOrUpdateSong(songModel, folder);
            AddOrUpdateSongTag(songModel, song);
        }
    }

    private Song AddOrUpdateSong(SongModel songModel, Folder folder)
    {
        string shortFileName = Path.GetFileName(songModel.FileName);

        var song = context.Songs.FirstOrDefault(x => x.Folder == folder && string.Equals(x.FileName, shortFileName, StringComparison.OrdinalIgnoreCase));

        if (song == null)
        {
            song = new()
            {
                FileName = shortFileName,
                Folder = folder
            };

            context.Songs.Add(song);
        }

        song.Size = (long)songModel.Size;
        song.Duration = songModel.Length.Ticks;
        song.Modified = songModel.LastModified;
        song.BitRate = (int)songModel.BitRate;
        song.SampleRate = (int)songModel.SampleRate;

        return song;
    }

    private SongTag AddOrUpdateSongTag(SongModel songModel, Song song)
    {
        var songTag = context.SongTags.FirstOrDefault(x => x.Song == song);

        if (songTag == null)
        {
            songTag = new SongTag()
            {
                Song = song
            };

            context.SongTags.Add(songTag);
        }

        songTag.Title = songModel.Title;
        songTag.Album = songModel.Album;
        songTag.Artist = string.Join(";", songModel.Artists);
        songTag.AlbumArtist = string.Join(";", songModel.AlbumArtists);
        songTag.TrackNumber = songModel.TrackNumber;
        songTag.DiscNumber = songModel.DiscNumber;
        songTag.Genre = songModel.Genre;
        songTag.ReleaseDate = songModel.Year != null ? new DateTime(songModel.Year.Value, 1, 1) : null;

        return songTag;
    }
}