using PkmnLib.Dynamic.Models;
using PkmnLib.Static;
using PkmnLib.Static.Species;
using PkmnLib.Tests.Integration;

namespace PkmnLib.Tests.Dynamic;

public class PokemonStatBoostTests
{
    private static IPokemon CreatePokemon()
    {
        var library = LibraryHelpers.LoadLibrary();
        if (!library.StaticLibrary.Species.TryGet("bulbasaur", out var species))
            throw new InvalidOperationException("Failed to load bulbasaur species.");
        return new PokemonImpl(library, species, species.GetDefaultForm(), new AbilityIndex
        {
            Index = 0,
            IsHidden = false,
        }, 50, 0, Gender.Male, 0, "hardy");
    }

    [Test]
    public async Task ChangeStatBoost_IncreasesStatBoost()
    {
        var pokemon = CreatePokemon();

        var changed = pokemon.ChangeStatBoost(Statistic.Attack, 2, true, false);

        await Assert.That(changed).IsTrue();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo((sbyte)2);
    }

    [Test]
    public async Task ChangeStatBoost_DecreasesStatBoost()
    {
        var pokemon = CreatePokemon();

        var changed = pokemon.ChangeStatBoost(Statistic.Attack, -2, true, false);

        await Assert.That(changed).IsTrue();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo((sbyte)-2);
    }

    [Test]
    public async Task ChangeStatBoost_ReturnsFalseWhenChangeIsZero()
    {
        var pokemon = CreatePokemon();

        var changed = pokemon.ChangeStatBoost(Statistic.Attack, 0, true, false);

        await Assert.That(changed).IsFalse();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo((sbyte)0);
    }

    [Test]
    public async Task ChangeStatBoost_ReturnsFalseWhenMaxCapReached()
    {
        var pokemon = CreatePokemon();

        // Raise the stat boost all the way to the maximum of +6.
        var raised = pokemon.ChangeStatBoost(Statistic.Attack, StatBoostStatisticSet.MaxStatBoost, true, false);
        await Assert.That(raised).IsTrue();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo(StatBoostStatisticSet.MaxStatBoost);

        // The cap has been reached, so a further increase should do nothing and report failure.
        var changed = pokemon.ChangeStatBoost(Statistic.Attack, 1, true, false);

        await Assert.That(changed).IsFalse();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo(StatBoostStatisticSet.MaxStatBoost);
    }

    [Test]
    public async Task ChangeStatBoost_ReturnsFalseWhenMinCapReached()
    {
        var pokemon = CreatePokemon();

        // Lower the stat boost all the way to the minimum of -6.
        var lowered = pokemon.ChangeStatBoost(Statistic.Attack, StatBoostStatisticSet.MinStatBoost, true, false);
        await Assert.That(lowered).IsTrue();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo(StatBoostStatisticSet.MinStatBoost);

        // The floor has been reached, so a further decrease should do nothing and report failure.
        var changed = pokemon.ChangeStatBoost(Statistic.Attack, -1, true, false);

        await Assert.That(changed).IsFalse();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo(StatBoostStatisticSet.MinStatBoost);
    }

    [Test]
    public async Task ChangeStatBoost_ClampsToCapButStillReportsChangeWhenPartiallyApplied()
    {
        var pokemon = CreatePokemon();

        // Raise to +5, one below the cap.
        await Assert.That(pokemon.ChangeStatBoost(Statistic.Attack, 5, true, false)).IsTrue();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo((sbyte)5);

        // Asking for +4 can only apply +1 before hitting the cap. Since a change is still made, it reports success.
        var changed = pokemon.ChangeStatBoost(Statistic.Attack, 4, true, false);

        await Assert.That(changed).IsTrue();
        await Assert.That(pokemon.StatBoost.Attack).IsEqualTo(StatBoostStatisticSet.MaxStatBoost);
    }
}