using Avalonia.Input.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.Input;
using Harmonia.Core.Engine;
using Harmonia.Core.Models;
using Harmonia.Core.Player;
using Harmonia.Core.Playlists;
using Harmonia.Core.Scanner;
using Harmonia.UI.Caching;
using Harmonia.UI.Platform;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Input;
using Timer = System.Timers.Timer;

namespace Harmonia.UI.ViewModels;

public class PlaylistViewModel : ViewModelBase
{
    private readonly IAudioPlayer _audioPlayer;
    private readonly IAudioBitmapCache _audioBitmapImageCache;
    private readonly IAudioFileScanner _audioFileScanner;
    private readonly IAudioEngine _audioEngine;
    private readonly IStorageProviderLocator _storageProviderLocator;
    private readonly IClipboardLocator _clipboardLocator;
    private readonly ConcurrentDictionary<string, Bitmap> _bitmapDictionary = [];

    private Timer? _filterTimer;

    public Playlist? Playlist { get; private set; }

    private PlaylistSong? _playingSong;
    public PlaylistSong? PlayingSong
    {
        get
        {
            return _playingSong;
        }
        set
        {
            SetProperty(ref _playingSong, value);
        }
    }

    private ObservableCollection<PlaylistSong> _playlistSongs = [];
    public ObservableCollection<PlaylistSong> PlaylistSongs
    {
        get
        {
            return _playlistSongs;
        }
        set
        {
            _playlistSongs = value;
            OnPropertyChanged();
        }
    }

    private string? _filter;
    public string? Filter
    {
        get
        {
            return _filter;
        }
        set
        {
            _filter = value;
            OnPropertyChanged();
            RestartFilterTimer();
        }
    }

    private ObservableCollection<PlaylistSong> _filteredPlaylistSongs = [];
    public ObservableCollection<PlaylistSong> FilteredPlaylistSongs
    {
        get
        {
            return _filteredPlaylistSongs;
        }
        set
        {
            SetProperty(ref _filteredPlaylistSongs, value);
        }
    }

    private ObservableCollection<PlaylistSong> _selectedPlaylistSongs = [];
    public ObservableCollection<PlaylistSong> SelectedPlaylistSongs
    {
        get
        {
            return _selectedPlaylistSongs;
        }
        set
        {
            _selectedPlaylistSongs = value;
            OnPropertyChanged();
        }
    }

    public ICommand PlaySongCommand => new AsyncRelayCommand(PlaySongAsync, AreSongsSelected);
    public ICommand AddFilesCommand => new AsyncRelayCommand(AddFilesAsync);
    public ICommand AddFolderCommand => new AsyncRelayCommand(AddFolderAsync);
    public ICommand RemoveSongsCommand => new RelayCommand(RemoveSongs, AreSongsSelected);
    public ICommand CutSongsCommand => new AsyncRelayCommand(CutSongsAsync, AreSongsSelected);
    public ICommand CopySongsCommand => new AsyncRelayCommand(CopySongsAsync, AreSongsSelected);
    public ICommand PasteSongsCommand => new AsyncRelayCommand(PasteSongsAsync, CanPasteSongs);
    public ICommand OpenFileLocationCommand => new RelayCommand(OpenFileLocation, AreSongsSelected);
    public ICommand RefreshTagsCommand => new RelayCommand(RefreshTags);
    public ICommand RemoveMissingSongsCommand => new RelayCommand(RemoveMissingSongs);
    public ICommand RemoveDuplicateSongsCommand => new RelayCommand(RemoveDuplicateSongs);

    public PlaylistViewModel(IAudioPlayer audioPlayer, IAudioBitmapCache audioBitmapImageCache,
        IAudioFileScanner audioFileScanner,
        IAudioEngine audioEngine,
        IStorageProviderLocator storageProviderLocator,
        IClipboardLocator clipboardLocator)
    {
        _audioPlayer = audioPlayer;
        _audioPlayer.PlaylistChanged += OnAudioPlayerPlaylistChanged;
        _audioPlayer.PlayingSongChanged += OnAudioPlayerPlayingSongChanged;

        _audioBitmapImageCache = audioBitmapImageCache;
        _audioFileScanner = audioFileScanner;
        _audioEngine = audioEngine;
        _storageProviderLocator = storageProviderLocator;
        _clipboardLocator = clipboardLocator;
    }

    private void OnAudioPlayerPlaylistChanged(object? sender, EventArgs e)
    {
        if (Playlist != null)
        {
            Playlist.PlaylistUpdated -= OnPlaylistUpdated;
        }

        Playlist = _audioPlayer.Playlist;

        if (Playlist != null)
        {
            Playlist.PlaylistUpdated += OnPlaylistUpdated;
        }

        PlaylistSong[] playlistSongs = _audioPlayer.Playlist?.Songs.ToArray() ?? [];

        PlaylistSongs = [.. playlistSongs];
        UpdateFilteredSongs();
    }

    private void OnPlaylistUpdated(object? sender, PlaylistUpdatedEventArgs e)
    {
        switch (e.Action)
        {
            case PlaylistUpdateAction.Add:
                Dispatcher.UIThread.Invoke(() => AddSongs(e.Songs, e.Index));
                break;
            case PlaylistUpdateAction.Remove:
                Dispatcher.UIThread.Invoke(() => RemoveSongsFromCollection(e.Songs));
                break;
        }
    }

    private void AddSongs(PlaylistSong[] playlistSongs, int index = 0)
    {
        // TODO: Performance improvements
        int currentIndex = index;

        foreach (PlaylistSong playlistSong in playlistSongs)
        {
            PlaylistSongs.Insert(currentIndex++, playlistSong);
        }

        UpdateFilteredSongs();
    }

    private void RemoveSongsFromCollection(PlaylistSong[] playlistSongs)
    {
        foreach (PlaylistSong playlistSong in playlistSongs)
        {
            PlaylistSongs.Remove(playlistSong);
        }

        UpdateFilteredSongs();
    }

    private void OnAudioPlayerPlayingSongChanged(object? sender, EventArgs e)
    {
        //OnPropertyChanged(nameof(PlayingSong));
        PlayingSong = _audioPlayer.PlayingSong;
    }

    public async Task PlaySongAsync(PlaylistSong playlistSong)
    {
        await _audioPlayer.LoadAsync(playlistSong, PlaybackMode.LoadAndPlay);
    }

    public async Task<Bitmap?> GetBitmapAsync(PlaylistSong playlistSong, CancellationToken cancellationToken)
    {
        return await _audioBitmapImageCache.GetAsync(playlistSong.Song, cancellationToken);
    }

    #region Filtering

    private void RestartFilterTimer()
    {
        if (_filterTimer == null)
        {
            _filterTimer = new Timer(300);
            _filterTimer.Elapsed += OnFilterTimerElapsed;
            _filterTimer.Start();
        }
        else
        {
            _filterTimer.Interval = 300;
        }
    }

    private void OnFilterTimerElapsed(object? sender, ElapsedEventArgs e)
    {
        if (_filterTimer == null)
            return;

        _filterTimer.Stop();
        _filterTimer.Dispose();
        _filterTimer = null;

        Dispatcher.UIThread.Invoke(UpdateFilteredSongs);
    }

    private void UpdateFilteredSongs()
    {
        if (Playlist == null)
            return;

        List<PlaylistSong> filteredPlaylistSongs = [.. Playlist.Songs.Where(playlistSong => IsFiltered(playlistSong.Song))];
        FilteredPlaylistSongs = [.. filteredPlaylistSongs];
    }

    private bool IsFiltered(Song song)
    {
        if (string.IsNullOrWhiteSpace(Filter))
            return true;

        var shortFileName = Path.GetFileName(song.FileName);

        if (shortFileName.Contains(Filter, StringComparison.OrdinalIgnoreCase))
            return true;

        if (string.IsNullOrWhiteSpace(song.Title) == false && song.Title.Contains(Filter, StringComparison.OrdinalIgnoreCase))
            return true;

        if (string.IsNullOrWhiteSpace(song.Album) == false && song.Album.Contains(Filter, StringComparison.OrdinalIgnoreCase))
            return true;

        if (song.AlbumArtists.Any(x => x.Contains(Filter, StringComparison.OrdinalIgnoreCase)))
            return true;

        if (song.Artists.Any(x => x.Contains(Filter, StringComparison.OrdinalIgnoreCase)))
            return true;

        return false;
    }

    #endregion

    #region Commands

    private async Task PlaySongAsync()
    {
        if (SelectedPlaylistSongs.Count == 0)
            return;

        await _audioPlayer.LoadAsync(SelectedPlaylistSongs[0], PlaybackMode.LoadAndPlay);
    }

    private bool AreSongsSelected()
    {
        return SelectedPlaylistSongs.Count > 0;
    }

    private async Task AddFilesAsync(CancellationToken cancellationToken)
    {
        if (Playlist == null)
            return;

        IStorageProvider? storageProvider = _storageProviderLocator.Get();

        if (storageProvider == null)
            return;

        FilePickerOpenOptions openOptions = new()
        {
            FileTypeFilter = [GetAudioFileTypes()],
            AllowMultiple = true
        };

        IReadOnlyList<IStorageFile> result = await storageProvider.OpenFilePickerAsync(openOptions);
        string[] fileNames = [.. result.Select(file => file.TryGetLocalPath() ?? string.Empty)];

        Song[] songs = await _audioFileScanner.GetSongsAsync(fileNames, cancellationToken);
        Playlist.AddSongs(songs);
    }

    private FilePickerFileType GetAudioFileTypes()
    {
        return new("Audo Files")
        {
            Patterns = [.. _audioEngine.SupportedFormats]
        };
    }

    private async Task AddFolderAsync(CancellationToken cancellationToken)
    {
        if (Playlist == null)
            return;

        IStorageProvider? storageProvider = _storageProviderLocator.Get();

        if (storageProvider == null)
            return;

        FolderPickerOpenOptions options = new()
        {
            AllowMultiple = true
        };

        IReadOnlyList<IStorageFolder> folders = await storageProvider.OpenFolderPickerAsync(options);

        if (folders.Count == 0)
            return;

        string? path = folders[0].TryGetLocalPath();

        if (string.IsNullOrWhiteSpace(path))
            return;

        Song[] songs = await _audioFileScanner.GetSongsFromPathAsync(path, cancellationToken);
        Playlist.AddSongs(songs);
    }

    public async Task AddFilesAsync(string[] fileNames, CancellationToken cancellationToken)
    {
        if (Playlist == null)
            return;

        Song[] songs = await _audioFileScanner.GetSongsAsync(fileNames, cancellationToken);
        Playlist.AddSongs(songs);
    }

    public async Task AddFolderAsync(string path, CancellationToken cancellationToken)
    {
        if (Playlist == null)
            return;

        Song[] songs = await _audioFileScanner.GetSongsFromPathAsync(path, cancellationToken);
        Playlist.AddSongs(songs);
    }

    private void RemoveSongs()
    {
        if (Playlist == null)
            return;

        if (SelectedPlaylistSongs.Count == 0)
            return;

        PlaylistSong[] playlistSongs = [.. SelectedPlaylistSongs];

        Playlist.RemoveSongs(playlistSongs);
    }

    private async Task CutSongsAsync()
    {
        if (SelectedPlaylistSongs.Count == 0)
            return;

        await CopySelectedSongsToClipboardAsync();
    }

    private async Task CopySongsAsync()
    {
        if (SelectedPlaylistSongs.Count == 0)
            return;

        await CopySelectedSongsToClipboardAsync();
    }

    private async Task CopySelectedSongsToClipboardAsync()
    {
        IClipboard? clipboard = _clipboardLocator.Get();

        if (clipboard == null)
            return;

        Song[] songs = [.. SelectedPlaylistSongs.Select(playlistSong => playlistSong.Song)];

        await clipboard.SetTextAsync(JsonSerializer.Serialize(songs));
    }

    //private async Task CopySelectedSongsToClipboard2Async()
    //{
    //    IClipboard? clipboard = Clipboard.Get();

    //    if (clipboard == null)
    //        return;

    //    Song[] songs = [.. SelectedPlaylistSongs.Select(playlistSong => playlistSong.Song)];

    //    DataObject dataObject = new();
    //    dataObject.Set(DataFormats.Text, JsonSerializer.Serialize(songs));

    //    await clipboard.SetDataObjectAsync(dataObject);
    //}

    private bool CanPasteSongs()
    {
        return GetSongsFromClipboardAsync().Result.Length > 0;
    }

    private async Task PasteSongsAsync()
    {
        if (Playlist == null || SelectedPlaylistSongs.Count == 0)
            return;

        int selectedPlaylistSongIndex = Playlist.Songs.IndexOf(SelectedPlaylistSongs[0]);

        if (selectedPlaylistSongIndex == -1)
            return;

        Song[] songs = await GetSongsFromClipboardAsync();

        Playlist.AddSongs(songs, selectedPlaylistSongIndex + 1);
    }

    private async Task<Song[]> GetSongsFromClipboardAsync()
    {
        IClipboard? clipboard = _clipboardLocator.Get();

        if (clipboard == null)
            return [];

        string? clipboardText = await clipboard.TryGetTextAsync();

        if (string.IsNullOrWhiteSpace(clipboardText))
            return [];

        try
        {
            return JsonSerializer.Deserialize<Song[]>(clipboardText) ?? [];
        }
        catch (JsonException)
        {
            return [];
        }
    }

    private void OpenFileLocation()
    {
        if (SelectedPlaylistSongs.Count == 0)
            return;

        string argument = "/select, \"" + SelectedPlaylistSongs[0].Song.FileName + "\"";
        Process.Start("explorer.exe", argument);
    }

    private void RefreshTags()
    {
        //Playlist?.RefreshTags();
    }

    private void RemoveMissingSongs()
    {
        Playlist?.RemoveMissingSongs();
    }

    private void RemoveDuplicateSongs()
    {
        Playlist?.RemoveDuplicateSongs();
    }

    #endregion
}