using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;

namespace PkmnLib.Tests.Dynamic;

public class ChoiceQueueTests
{
    [Test]
    public async Task ChoiceQueue_HighSpeedFirstWhenPriorityEqual()
    {
        var pokemon1 = Substitute.For<IPokemon>();
        var pokemon2 = Substitute.For<IPokemon>();

        var choice1 = Substitute.For<IMoveChoice>();
        choice1.User.Returns(pokemon1);
        choice1.Priority.Returns((sbyte)0);
        choice1.Speed.Returns((uint)100);
        var choice2 = Substitute.For<IMoveChoice>();
        choice2.User.Returns(pokemon2);
        choice2.Priority.Returns((sbyte)0);
        choice2.Speed.Returns((uint)50);

        var queue = new BattleChoiceQueue([choice1, choice2]);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice1);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice2);
    }

    [Test]
    public async Task ChoiceQueue_HighPriorityFirst()
    {
        var pokemon1 = Substitute.For<IPokemon>();
        var pokemon2 = Substitute.For<IPokemon>();

        var choice1 = Substitute.For<IMoveChoice>();
        choice1.User.Returns(pokemon1);
        choice1.Priority.Returns((sbyte)0);
        choice1.Speed.Returns((uint)100);
        var choice2 = Substitute.For<IMoveChoice>();
        choice2.User.Returns(pokemon2);
        choice2.Priority.Returns((sbyte)1);
        choice2.Speed.Returns((uint)50);

        var queue = new BattleChoiceQueue([choice1, choice2]);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice2);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice1);
    }

    [Test]
    public async Task ChoiceQueue_MovePokemonChoiceNext()
    {
        var pokemon1 = Substitute.For<IPokemon>();
        var pokemon2 = Substitute.For<IPokemon>();
        var pokemon3 = Substitute.For<IPokemon>();
        var pokemon4 = Substitute.For<IPokemon>();

        var choice1 = Substitute.For<IMoveChoice>();
        choice1.User.Returns(pokemon1);
        choice1.Speed.Returns((uint)4);
        var choice2 = Substitute.For<IMoveChoice>();
        choice2.User.Returns(pokemon2);
        choice2.Speed.Returns((uint)3);
        var choice3 = Substitute.For<IMoveChoice>();
        choice3.User.Returns(pokemon3);
        choice3.Speed.Returns((uint)2);
        var choice4 = Substitute.For<IMoveChoice>();
        choice4.User.Returns(pokemon4);
        choice4.Speed.Returns((uint)1);

        var queue = new BattleChoiceQueue([choice1, choice2, choice3, choice4]);
        var result = queue.MovePokemonChoiceNext(pokemon3);
        await Assert.That(result).IsTrue();
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice3);
    }

    [Test]
    public async Task ChoiceQueue_MovePokemonChoiceNextFailsIfAlreadyExecuted()
    {
        var pokemon1 = Substitute.For<IPokemon>();
        var pokemon2 = Substitute.For<IPokemon>();
        var pokemon3 = Substitute.For<IPokemon>();
        var pokemon4 = Substitute.For<IPokemon>();

        var choice1 = Substitute.For<IMoveChoice>();
        choice1.User.Returns(pokemon1);
        choice1.Speed.Returns((uint)4);
        var choice2 = Substitute.For<IMoveChoice>();
        choice2.User.Returns(pokemon2);
        choice2.Speed.Returns((uint)3);
        var choice3 = Substitute.For<IMoveChoice>();
        choice3.User.Returns(pokemon3);
        choice3.Speed.Returns((uint)2);
        var choice4 = Substitute.For<IMoveChoice>();
        choice4.User.Returns(pokemon4);
        choice4.Speed.Returns((uint)1);

        var queue = new BattleChoiceQueue([choice1, choice2, choice3, choice4]);
        queue.Dequeue();
        var result = queue.MovePokemonChoiceNext(pokemon1);
        await Assert.That(result).IsFalse();
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice2);
    }

    [Test]
    public async Task ChoiceQueue_MovePokemonChoiceLast()
    {
        var pokemon1 = Substitute.For<IPokemon>();
        var pokemon2 = Substitute.For<IPokemon>();
        var pokemon3 = Substitute.For<IPokemon>();
        var pokemon4 = Substitute.For<IPokemon>();

        var choice1 = Substitute.For<IMoveChoice>();
        choice1.User.Returns(pokemon1);
        choice1.Speed.Returns((uint)4);
        var choice2 = Substitute.For<IMoveChoice>();
        choice2.User.Returns(pokemon2);
        choice2.Speed.Returns((uint)3);
        var choice3 = Substitute.For<IMoveChoice>();
        choice3.User.Returns(pokemon3);
        choice3.Speed.Returns((uint)2);
        var choice4 = Substitute.For<IMoveChoice>();
        choice4.User.Returns(pokemon4);
        choice4.Speed.Returns((uint)1);

        var queue = new BattleChoiceQueue([choice1, choice2, choice3, choice4]);
        var result = queue.MovePokemonChoiceLast(pokemon2);
        await Assert.That(result).IsTrue();
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice1);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice3);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice4);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice2);
    }

    [Test]
    public async Task ChoiceQueue_MovePokemonChoiceLastFailsIfAlreadyExecuted()
    {
        var pokemon1 = Substitute.For<IPokemon>();
        var pokemon2 = Substitute.For<IPokemon>();
        var pokemon3 = Substitute.For<IPokemon>();
        var pokemon4 = Substitute.For<IPokemon>();

        var choice1 = Substitute.For<IMoveChoice>();
        choice1.User.Returns(pokemon1);
        choice1.Speed.Returns((uint)4);
        var choice2 = Substitute.For<IMoveChoice>();
        choice2.User.Returns(pokemon2);
        choice2.Speed.Returns((uint)3);
        var choice3 = Substitute.For<IMoveChoice>();
        choice3.User.Returns(pokemon3);
        choice3.Speed.Returns((uint)2);
        var choice4 = Substitute.For<IMoveChoice>();
        choice4.User.Returns(pokemon4);
        choice4.Speed.Returns((uint)1);

        var queue = new BattleChoiceQueue([choice1, choice2, choice3, choice4]);
        queue.Dequeue();
        var result = queue.MovePokemonChoiceLast(pokemon1);
        await Assert.That(result).IsFalse();
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice2);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice3);
        await Assert.That((IMoveChoice)queue.Dequeue()!).IsEqualTo(choice4);
    }
}