using PkmnLib.Dynamic.Models;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Dynamic.ScriptHandling.Registry;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Plugin.Gen7.Scripts.Side;
using PkmnLib.Static.Moves;

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

/// <summary>
/// Tests for the <see cref="CraftyShield"/> move script and the <see cref="CraftyShieldEffect"/> it
/// applies. Gen VII Bulbapedia behavior: "Crafty Shield protects all Pokémon on the user's side of the
/// field from status moves."
/// </summary>
public class CraftyShieldTests
{
    private static (CraftyShield script, IExecutingMove move, IScriptSet sideVolatile) CreateTestSetup()
    {
        var script = new CraftyShield();

        var side = Substitute.For<IBattleSide>();
        // Use a real script set so the side script added by Crafty Shield can be inspected afterwards.
        var sideVolatile = new ScriptSet(side);
        side.VolatileScripts.Returns(sideVolatile);
        side.GetScripts().Returns(_ => new ScriptIterator(new List<IEnumerable<ScriptContainer>>()));

        var battle = Substitute.For<IBattle>();
        battle.Sides.Returns(new[] { side });
        var battleData = Substitute.For<IPokemonBattleData>();
        battleData.Battle.Returns(battle);
        battleData.SideIndex.Returns((byte)0);

        var user = Substitute.For<IPokemon>();
        user.BattleData.Returns(battleData);
        var move = Substitute.For<IExecutingMove>();
        move.User.Returns(user);

        return (script, move, sideVolatile);
    }

    private static IExecutingMove CreateExecutingMoveOfCategory(MoveCategory category)
    {
        var useMove = Substitute.For<IMoveData>();
        useMove.Category.Returns(category);
        var move = Substitute.For<IExecutingMove>();
        move.UseMove.Returns(useMove);
        return move;
    }

    /// <summary>
    /// Bulbapedia: "Crafty Shield protects all Pokémon on the user's side of the field" — using the move
    /// attaches the <see cref="CraftyShieldEffect"/> to the user's side.
    /// </summary>
    [Test]
    public async Task OnSecondaryEffect_Used_AddsCraftyShieldEffectToUsersSide()
    {
        // Arrange
        var (script, move, sideVolatile) = CreateTestSetup();

        // Act
        script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);

        // Assert
        await Assert.That(sideVolatile.Contains(ScriptUtils.ResolveName<CraftyShieldEffect>())).IsTrue();
    }

    /// <summary>
    /// Bulbapedia: the shield protects "from status moves" — an incoming status move is stopped.
    /// </summary>
    [Test]
    public async Task CraftyShieldEffect_StatusMove_IsStopped()
    {
        // Arrange
        var effect = new CraftyShieldEffect();
        var move = CreateExecutingMoveOfCategory(MoveCategory.Status);
        var stop = false;

        // Act
        effect.StopBeforeMove(move, ref stop);

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

    /// <summary>
    /// Bulbapedia: Crafty Shield only blocks status moves — damaging moves go through it unhindered.
    /// </summary>
    [Test, Arguments(MoveCategory.Physical), Arguments(MoveCategory.Special)]
    public async Task CraftyShieldEffect_DamagingMove_IsNotStopped(MoveCategory category)
    {
        // Arrange
        var effect = new CraftyShieldEffect();
        var move = CreateExecutingMoveOfCategory(category);
        var stop = false;

        // Act
        effect.StopBeforeMove(move, ref stop);

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

    /// <summary>
    /// Technical test: outside of battle (no battle data) no shield can be raised and nothing happens.
    /// </summary>
    [Test]
    public async Task OnSecondaryEffect_NoBattleData_DoesNothing()
    {
        // Arrange
        var (script, move, sideVolatile) = CreateTestSetup();
        move.User.BattleData.Returns((IPokemonBattleData?)null);

        // Act
        script.OnSecondaryEffect(move, Substitute.For<IPokemon>(), 0);

        // Assert - no effect is added
        await Assert.That(sideVolatile.Contains(ScriptUtils.ResolveName<CraftyShieldEffect>())).IsFalse();
    }
}