using Harmonia.Core.Engine;
using Harmonia.Core.Models;
using Harmonia.Core.Player;
using Harmonia.Core.Playlists;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using Shouldly;

namespace Harmonia.Tests;

internal class TestAudioPlayer(IAudioEngine audioEngine, IPlaylistRepository playlistRepository)
    : AudioPlayer(audioEngine, playlistRepository)
{
    internal void SetPlaylist(Playlist playlist)
    {
        Playlist = playlist;
    }

    internal int GetPlayingSongIndex()
    {
        if (Playlist == null || PlayingSong == null)
            return -1;

        return Playlist.Songs.IndexOf(PlayingSong);
    }
}

public class AudioPlayerTests
{
    private readonly IAudioEngine _audioEngine;
    private readonly IPlaylistRepository _playlistRepository;
    private readonly PlaylistSong[] _playlistSongs;
    private readonly TestAudioPlayer _audioPlayer;

    public AudioPlayerTests()
    {
        _audioEngine = Substitute.For<IAudioEngine>();
        _audioEngine.LoadAsync("Song1.mp3").Returns(true);
        _audioEngine.LoadAsync("Song2.mp3").Returns(false);
        _audioEngine.LoadAsync("Song3.mp3").Returns(true);
        _audioEngine.LoadAsync("Song4.mp3").Throws(new FileNotFoundException());
        _audioEngine.LoadAsync("Song5.mp3").Returns(false);
        _audioEngine.LoadAsync("Song6.mp3").Returns(true);
        _audioEngine.LoadAsync("Song7.mp3").Returns(true);

        Song[] songs =
        [
            new Song() { FileName = "Song1.mp3" },
            new Song() { FileName = "Song2.mp3" },
            new Song() { FileName = "Song3.mp3" },
            new Song() { FileName = "Song4.mp3" },
            new Song() { FileName = "Song5.mp3" },
            new Song() { FileName = "Song6.mp3" },
            new Song() { FileName = "Song7.mp3" }
        ];

        Playlist playlist = new()
        {
            Name = "Playlist1"
        };

        playlist.AddSongs(songs);

        _playlistSongs = [.. playlist.Songs];

        _playlistRepository = Substitute.For<IPlaylistRepository>();
        _playlistRepository.Get().Returns([playlist]);

        _audioPlayer = new TestAudioPlayer(_audioEngine, _playlistRepository);
        _audioPlayer.SetPlaylist(playlist);
    }

    [Theory]
    [InlineData(0, true)]
    [InlineData(1, false)]
    [InlineData(2, true)]
    [InlineData(3, false)]
    [InlineData(4, false)]
    [InlineData(5, true)]
    [InlineData(6, true)]
    [InlineData(7, false)]
    public async Task Load_Song(int index, bool expectedResult)
    {
        bool result = await _audioPlayer.LoadAsync(index);
        result.ShouldBe(expectedResult);
    }

    [Theory]
    [InlineData(0, 2)]
    [InlineData(2, 5)]
    [InlineData(5, 6)]
    [InlineData(6, 0)]
    public async Task Load_Next_Song(int index, int expectedResult)
    {
        await _audioPlayer.LoadAsync(index);
        await _audioPlayer.NextAsync();

        int currentIndex = _audioPlayer.GetPlayingSongIndex();

        currentIndex.ShouldBe(expectedResult);
    }

    [Theory]
    [InlineData(0, 0, RepeatState.Off, 0)]
    [InlineData(0, 6, RepeatState.RepeatAll, 0)]
    [InlineData(6, 5, RepeatState.Off, 0)]
    [InlineData(6, 6, RepeatState.Off, 6)]
    public async Task Load_Previous_Song(int index, int expectedResult, RepeatState repeatState = RepeatState.Off, double position = 0)
    {
        await _audioPlayer.LoadAsync(index, PlaybackMode.LoadAndPlay);
        _audioPlayer.RepeatState = repeatState;
        _audioPlayer.Position = position;

        await _audioPlayer.PreviousAsync();

        int currentIndex = _audioPlayer.GetPlayingSongIndex();

        currentIndex.ShouldBe(expectedResult);
    }

    [Theory]
    [InlineData(0, 2, RepeatState.Off)]
    [InlineData(0, 0, RepeatState.RepeatOne)]
    public async Task Song_Finshed(int index, int expectedResult, RepeatState repeatState = RepeatState.Off)
    {
        await _audioPlayer.LoadAsync(index, PlaybackMode.LoadAndPlay);
        _audioPlayer.RepeatState = repeatState;

        TaskCompletionSource<bool> taskCompletionSource = new();

        _audioEngine.StreamFinished += (sender, eventArgs) => { taskCompletionSource.SetResult(true); };
        _audioEngine.StreamFinished += Raise.EventWith<EventArgs>(_audioEngine, new());

        await taskCompletionSource.Task;

        int currentIndex = _audioPlayer.GetPlayingSongIndex();

        currentIndex.ShouldBe(expectedResult);
    }
}