using Harmonia.Core.Engine;
using Harmonia.Core.Playlists;
using System.ComponentModel;

namespace Harmonia.Core.Player;

public class AudioPlayer : IAudioPlayer
{
    private readonly IAudioEngine _audioEngine;
    private readonly IPlaylistRepository _playlistRepository;

    private Playlist? _playlist;
    public Playlist? Playlist
    {
        get
        {
            return _playlist;
        }
        protected set
        {
            _playlist = value;
            NotifyPropertyChanged(nameof(Playlist));
            PlaylistChanged?.Invoke(this, new());
        }
    }

    protected PlaylistSong? CurrentPlaylistSong { get; set; }

    private PlaylistSong? _playingSong;
    public PlaylistSong? PlayingSong
    {
        get
        {
            return _playingSong;
        }
        protected set
        {
            _playingSong = value;
            NotifyPropertyChanged(nameof(PlayingSong));
            PlayingSongChanged?.Invoke(this, new());
        }
    }

    public double Position
    {
        get
        {
            return _audioEngine.Position.TotalSeconds;
        }
        set
        {
            _audioEngine.Position = TimeSpan.FromSeconds(value);
            NotifyPropertyChanged(nameof(Position));
        }
    }

    private RepeatState _repeatState;
    public RepeatState RepeatState
    {
        get
        {
            return _repeatState;
        }
        set
        {
            _repeatState = value;
            NotifyPropertyChanged(nameof(RepeatState));
        }
    }

    public double Volume
    {
        get
        {
            return _audioEngine.Volume;
        }
        set
        {
            // Should the mute logic be here instead of the in view model?
            _audioEngine.Volume = Convert.ToSingle(value);
            NotifyPropertyChanged(nameof(Volume));
        }
    }

    private bool _isRandom;
    public bool IsRandom
    {
        get
        {
            return _isRandom;
        }
        set
        {
            _isRandom = value;
            NotifyPropertyChanged(nameof(IsRandom));
        }
    }

    public bool IsMuted
    {
        get
        {
            return _audioEngine.IsMuted;
        }
        set
        {
            _audioEngine.IsMuted = value;
            NotifyPropertyChanged(nameof(IsMuted));
        }
    }

    public AudioPlaybackState State => _audioEngine.State;

    protected virtual int PreviousSongSecondsThreshold => 5;

    public event EventHandler? PlaylistChanged;
    public event EventHandler? PlayingSongChanged;
    public event PropertyChangedEventHandler? PropertyChanged;

    public AudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository)
    {
        _audioEngine = audioEngine;
        _audioEngine.StreamFinished += OnAudioEngineStreamFinished;
        _audioEngine.StateChanged += OnMusicEngineStateChanged;

        _playlistRepository = playlistRepository;
    }

    private async void OnAudioEngineStreamFinished(object? sender, EventArgs e)
    {
        if (RepeatState == RepeatState.RepeatOne)
        {
            Position = 0;
            Play();
        }
        else
        {
            await NextAsync();
        }
    }

    private void OnMusicEngineStateChanged(object? sender, PlaybackStateChangedEventArgs e)
    {
        NotifyPropertyChanged(nameof(State));
    }

    public void Play()
    {
        _audioEngine.Play();
    }

    public void Pause()
    {
        _audioEngine.Pause();
    }

    public void Stop()
    {
        _audioEngine.Stop();
    }

    public async Task NextAsync()
    {
        if (Playlist == null || CurrentPlaylistSong == null || Playlist.Songs.Count == 0)
            return;

        if (IsRandom)
        {
            int randomIndex = new Random().Next(0, Playlist.Songs.Count - 1);
            await LoadAsync(randomIndex);
            return;
        }

        int currentIndex = Playlist.Songs.IndexOf(CurrentPlaylistSong);
        int nextIndex = currentIndex + 1;

        if (nextIndex > Playlist.Songs.Count - 1)
        {
            PlaybackMode playbackMode = RepeatState == RepeatState.RepeatAll
                ? PlaybackMode.LoadAndPlay
                : PlaybackMode.LoadOnly;

            await LoadAsync(0, playbackMode);
            return;
        }

        await LoadAsync(nextIndex);
    }

    public async Task PreviousAsync()
    {
        if (Playlist == null || CurrentPlaylistSong == null || Playlist.Songs.Count == 0)
            return;

        if (Position > PreviousSongSecondsThreshold)
        {
            Position = 0;
            return;
        }

        int currentIndex = Playlist.Songs.IndexOf(CurrentPlaylistSong);
        int nextIndex = currentIndex - 1;

        if (nextIndex < 0)
        {
            if (RepeatState == RepeatState.RepeatAll)
            {
                await LoadAsync(Playlist.Songs.Count - 1);
                return;
            }

            Stop();
            return;
        }

        await LoadAsync(nextIndex);
    }

    public async Task<bool> LoadAsync(int index, PlaybackMode mode = PlaybackMode.LoadAndPlay)
    {
        if (Playlist == null)
            return false;

        if (index < 0 || index > Playlist.Songs.Count - 1)
            return false;

        return await LoadAsync(Playlist.Songs[index], mode);
    }

    public async Task<bool> LoadAsync(PlaylistSong song, PlaybackMode mode = PlaybackMode.LoadAndPlay)
    {
        if (Playlist == null || Playlist.Songs.Contains(song) == false)
        {
            //Playlist? newPlaylist = _playlistRepository.GetPlaylist(song);

            Playlist? newPlaylist = _playlistRepository.Get().FirstOrDefault(playlist =>
                playlist.Songs.Contains(song));

            if (newPlaylist == null)
                return false;

            Playlist = newPlaylist;
        }

        CurrentPlaylistSong = song;

        bool isLoaded = await TryLoadAsync(song);

        if (isLoaded == false)
        {
            if (mode == PlaybackMode.LoadAndPlay)
            {
                await NextAsync();
            }

            return false;
        }

        PlayingSong = song;
        Position = 0;

        if (mode == PlaybackMode.LoadAndPlay)
        {
            Play();
        }

        return true;
    }

    private async Task<bool> TryLoadAsync(PlaylistSong song)
    {
        try
        {
            return await _audioEngine.LoadAsync(song.Song.FileName);
        }
        catch (Exception)
        {
            return false;
        }
    }

    protected void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}