using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Static;

namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;

/// <summary>
/// Tests for the <see cref="Belch"/> move script.
/// Gen VII Bulbapedia behavior: "Belch deals damage. It cannot be selected unless the user has previously eaten
/// a Berry in the current battle."
/// </summary>
public class BelchTests
{
    /// <summary>
    /// Creates a fully mocked test setup where the user's <see cref="IPokemonBattleData.ConsumedItems"/>
    /// contains one item per given <see cref="ItemCategory"/>.
    /// </summary>
    private static (Belch script, IMoveChoice choice) CreateTestSetup(params ItemCategory[] consumedItemCategories)
    {
        var script = new Belch();
        var user = Substitute.For<IPokemon>();

        var items = consumedItemCategories.Select(category =>
        {
            var item = Substitute.For<IItem>();
            item.Category.Returns(category);
            return item;
        }).ToArray();
        var battleData = Substitute.For<IPokemonBattleData>();
        battleData.ConsumedItems.Returns(items);
        user.BattleData.Returns(battleData);

        var choice = Substitute.For<IMoveChoice>();
        choice.User.Returns(user);

        return (script, choice);
    }

    /// <summary>
    /// Bulbapedia: "It cannot be selected unless the user has previously eaten a Berry in the current battle."
    /// A user that has eaten a Berry may select Belch.
    /// </summary>
    [Test]
    public async Task PreventMoveSelection_UserAteBerry_SelectionAllowed()
    {
        // Arrange
        var (script, choice) = CreateTestSetup(ItemCategory.Berry);
        var prevent = false;

        // Act
        script.PreventMoveSelection(choice, ref prevent);

        // Assert
        await Assert.That(prevent).IsFalse();
    }

    /// <summary>
    /// Bulbapedia: "It cannot be selected unless the user has previously eaten a Berry in the current battle."
    /// A user that has not consumed anything cannot select Belch.
    /// </summary>
    [Test]
    public async Task PreventMoveSelection_NoItemsConsumed_SelectionPrevented()
    {
        // Arrange
        var (script, choice) = CreateTestSetup();
        var prevent = false;

        // Act
        script.PreventMoveSelection(choice, ref prevent);

        // Assert
        await Assert.That(prevent).IsTrue();
    }

    /// <summary>
    /// Bulbapedia: "It cannot be selected unless the user has previously eaten a Berry in the current battle."
    /// Having consumed only non-Berry items does not satisfy the requirement.
    /// </summary>
    [Test, Arguments(ItemCategory.MiscItem), Arguments(ItemCategory.Medicine)]
    public async Task PreventMoveSelection_OnlyNonBerryItemConsumed_SelectionPrevented(ItemCategory category)
    {
        // Arrange
        var (script, choice) = CreateTestSetup(category);
        var prevent = false;

        // Act
        script.PreventMoveSelection(choice, ref prevent);

        // Assert
        await Assert.That(prevent).IsTrue();
    }

    /// <summary>
    /// Bulbapedia: "It cannot be selected unless the user has previously eaten a Berry in the current battle."
    /// The Berry does not have to be the only consumed item: a Berry among other consumed items is enough.
    /// </summary>
    [Test]
    public async Task PreventMoveSelection_BerryAmongOtherConsumedItems_SelectionAllowed()
    {
        // Arrange
        var (script, choice) = CreateTestSetup(ItemCategory.MiscItem, ItemCategory.Berry);
        var prevent = false;

        // Act
        script.PreventMoveSelection(choice, ref prevent);

        // Assert
        await Assert.That(prevent).IsFalse();
    }

    /// <summary>
    /// Technical test: outside of battle (no <see cref="IPokemon.BattleData"/>) the script does not prevent
    /// selection.
    /// </summary>
    [Test]
    public async Task PreventMoveSelection_NoBattleData_SelectionAllowed()
    {
        // Arrange
        var script = new Belch();
        var user = Substitute.For<IPokemon>();
        user.BattleData.Returns((IPokemonBattleData?)null);
        var choice = Substitute.For<IMoveChoice>();
        choice.User.Returns(user);
        var prevent = false;

        // Act
        script.PreventMoveSelection(choice, ref prevent);

        // Assert
        await Assert.That(prevent).IsFalse();
    }
}