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

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

/// <summary>
/// Tests for the <see cref="CoreEnforcer"/> move script.
/// Gen VII Bulbapedia behavior: "Core Enforcer deals damage. If the target has already used a move or had
/// a Bag item used on it by its Trainer in the same turn, Core Enforcer also suppresses the target's
/// Ability while it remains in battle."
/// </summary>
public class CoreEnforcerTests
{
    private static (CoreEnforcer script, IExecutingMove move, IMoveChoice currentChoice, IPokemon target, IHitData
        hitData, IBattle battle) CreateTestSetup()
    {
        var script = new CoreEnforcer();
        var battle = Substitute.For<IBattle>();
        var battleData = Substitute.For<IPokemonBattleData>();
        battleData.Battle.Returns(battle);
        var target = Substitute.For<IPokemon>();
        target.BattleData.Returns(battleData);

        var currentChoice = Substitute.For<IMoveChoice>();
        var move = Substitute.For<IExecutingMove>();
        move.MoveChoice.Returns(currentChoice);
        var hitData = Substitute.For<IHitData>();
        move.GetHitData(target, 0).Returns(hitData);

        return (script, move, currentChoice, target, hitData, battle);
    }

    private static void SetTurnChoices(IBattle battle, params ITurnChoice[] choices)
    {
        battle.PreviousTurnChoices.Returns(new List<IReadOnlyList<ITurnChoice>> { choices });
    }

    /// <summary>
    /// Bulbapedia: "If the target has already used a move [...] in the same turn, Core Enforcer also
    /// suppresses the target's Ability while it remains in battle."
    /// </summary>
    [Test]
    public void OnSecondaryEffect_TargetMovedEarlierInTurn_SuppressesTargetAbility()
    {
        // Arrange
        var (script, move, currentChoice, target, hitData, battle) = CreateTestSetup();
        var targetChoice = Substitute.For<IMoveChoice>();
        targetChoice.User.Returns(target);
        SetTurnChoices(battle, targetChoice, currentChoice);

        // Act
        script.OnSecondaryEffect(move, target, 0);

        // Assert
        target.Received(1).SuppressAbility();
        hitData.DidNotReceive().Fail();
    }

    /// <summary>
    /// Bulbapedia: the suppression also happens if the target "had a Bag item used on it by its Trainer in
    /// the same turn".
    /// </summary>
    [Test]
    public void OnSecondaryEffect_ItemUsedEarlierInTurn_SuppressesTargetAbility()
    {
        // Arrange
        var (script, move, currentChoice, target, hitData, battle) = CreateTestSetup();
        var itemChoice = Substitute.For<IItemChoice>();
        itemChoice.User.Returns(target);
        SetTurnChoices(battle, itemChoice, currentChoice);

        // Act
        script.OnSecondaryEffect(move, target, 0);

        // Assert
        target.Received(1).SuppressAbility();
        hitData.DidNotReceive().Fail();
    }

    /// <summary>
    /// Bulbapedia: the Ability is only suppressed "If the target has already used a move [...] in the same
    /// turn" — when Core Enforcer strikes before the target has acted, no suppression happens.
    /// </summary>
    [Test]
    public void OnSecondaryEffect_TargetActsLaterInTurn_DoesNotSuppressAbility()
    {
        // Arrange
        var (script, move, currentChoice, target, hitData, battle) = CreateTestSetup();
        var targetChoice = Substitute.For<IMoveChoice>();
        targetChoice.User.Returns(target);
        SetTurnChoices(battle, currentChoice, targetChoice);

        // Act
        script.OnSecondaryEffect(move, target, 0);

        // Assert
        target.DidNotReceive().SuppressAbility();
        hitData.Received(1).Fail();
    }

    /// <summary>
    /// Bulbapedia: suppression requires the target to have already acted — as the very first action of the
    /// turn, Core Enforcer never suppresses.
    /// </summary>
    [Test]
    public void OnSecondaryEffect_CoreEnforcerIsFirstActionOfTurn_DoesNotSuppressAbility()
    {
        // Arrange
        var (script, move, currentChoice, target, hitData, battle) = CreateTestSetup();
        SetTurnChoices(battle, currentChoice);

        // Act
        script.OnSecondaryEffect(move, target, 0);

        // Assert
        target.DidNotReceive().SuppressAbility();
        hitData.Received(1).Fail();
    }

    /// <summary>
    /// Technical test: a target without battle data (not in battle) is left untouched.
    /// </summary>
    [Test]
    public void OnSecondaryEffect_TargetHasNoBattleData_DoesNothing()
    {
        // Arrange
        var (script, move, _, target, hitData, _) = CreateTestSetup();
        target.BattleData.Returns((IPokemonBattleData?)null);

        // Act
        script.OnSecondaryEffect(move, target, 0);

        // Assert
        target.DidNotReceive().SuppressAbility();
        hitData.DidNotReceive().Fail();
    }

    /// <summary>
    /// Bulbapedia: the condition is that "the target has already used a move" — an action by a different
    /// Pokémon (e.g. the user's ally in a Double Battle) does not count as the target having acted.
    /// </summary>
    [Test]
    public void OnSecondaryEffect_OnlyOtherPokemonActedEarlier_DoesNotSuppressAbility()
    {
        // Arrange
        var (script, move, currentChoice, target, hitData, battle) = CreateTestSetup();
        var allyChoice = Substitute.For<IMoveChoice>();
        allyChoice.User.Returns(Substitute.For<IPokemon>());
        SetTurnChoices(battle, allyChoice, currentChoice);

        // Act
        script.OnSecondaryEffect(move, target, 0);

        // Assert
        target.DidNotReceive().SuppressAbility();
        hitData.Received(1).Fail();
    }

    /// <summary>
    /// Bulbapedia: "The move cannot suppress certain signature abilities including Multitype, Stance
    /// Change, Schooling, Comatose, Shields Down, Disguise, RKS System, Battle Bond, Power Construct".
    /// The script requests the suppression unconditionally; <see cref="IPokemon.SuppressAbility"/> refuses
    /// it when <see cref="IAbility.CanBeChanged"/> is false, so these abilities must carry that flag in the
    /// Gen7 data.
    /// </summary>
    [Test, Arguments("multitype"), Arguments("stance_change"), Arguments("schooling"), Arguments("comatose"),
     Arguments("shields_down"), Arguments("disguise"), Arguments("rks_system"), Arguments("battle_bond"),
     Arguments("power_construct")]
    public async Task OnSecondaryEffect_UnsuppressableAbility_IsProtectedByAbilityData(string abilityName)
    {
        // Arrange
        var library = LibraryHelpers.LoadLibrary();

        // Assert
        await Assert.That(library.StaticLibrary.Abilities.TryGet(new StringKey(abilityName), out var ability)).IsTrue();
        await Assert.That(ability!.CanBeChanged).IsFalse();
    }
}