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

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

/// <summary>
/// Tests for the <see cref="Attract"/> move script.
/// Gen VII Bulbapedia behavior: "The target becomes infatuated with the user if they have opposite genders.
/// [...] Attract has no effect if the user and target have the same gender, or if either of them is a
/// gender-unknown Pokémon (such as Magnemite). It also fails when used on a Pokémon that is already infatuated."
/// </summary>
public class AttractTests
{
    private static (Attract script, IExecutingMove move, IPokemon target, IScriptSet targetVolatile, IHitData hitData)
        CreateTestSetup(Gender userGender, Gender targetGender)
    {
        var script = new Attract();
        var move = Substitute.For<IExecutingMove>();
        var target = Substitute.For<IPokemon>();
        var hitData = Substitute.For<IHitData>();
        move.GetHitData(target, 0).Returns(hitData);

        var targetVolatile = Substitute.For<IScriptSet>();
        target.Volatile.Returns(targetVolatile);
        target.Gender.Returns(targetGender);

        var user = Substitute.For<IPokemon>();
        user.Gender.Returns(userGender);
        move.User.Returns(user);

        return (script, move, target, targetVolatile, hitData);
    }

    /// <summary>
    /// Bulbapedia: "The target becomes infatuated with the user if they have opposite genders."
    /// </summary>
    [Test, Arguments(Gender.Male, Gender.Female), Arguments(Gender.Female, Gender.Male)]
    public void OnSecondaryEffect_OppositeGenders_TargetBecomesInfatuated(Gender userGender, Gender targetGender)
    {
        // Arrange
        var (script, move, target, targetVolatile, hitData) = CreateTestSetup(userGender, targetGender);

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

        // Assert
        targetVolatile.Received(1).Add(Arg.Any<Infatuated>());
        hitData.DidNotReceive().Fail();
    }

    /// <summary>
    /// Bulbapedia: "Attract has no effect if the user and target have the same gender".
    /// </summary>
    [Test, Arguments(Gender.Male, Gender.Male), Arguments(Gender.Female, Gender.Female)]
    public void OnSecondaryEffect_SameGender_Fails(Gender userGender, Gender targetGender)
    {
        // Arrange
        var (script, move, target, targetVolatile, hitData) = CreateTestSetup(userGender, targetGender);

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

        // Assert
        hitData.Received(1).Fail();
        targetVolatile.DidNotReceive().Add(Arg.Any<Script>());
    }

    /// <summary>
    /// Bulbapedia: "Attract has no effect [...] if either of them is a gender-unknown Pokémon (such as Magnemite)."
    /// Covers a genderless user, a genderless target, and both being genderless.
    /// </summary>
    [Test, Arguments(Gender.Genderless, Gender.Female), Arguments(Gender.Male, Gender.Genderless),
     Arguments(Gender.Genderless, Gender.Genderless)]
    public void OnSecondaryEffect_GenderlessPokemon_Fails(Gender userGender, Gender targetGender)
    {
        // Arrange
        var (script, move, target, targetVolatile, hitData) = CreateTestSetup(userGender, targetGender);

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

        // Assert
        hitData.Received(1).Fail();
        targetVolatile.DidNotReceive().Add(Arg.Any<Script>());
    }

    /// <summary>
    /// Bulbapedia: "It also fails when used on a Pokémon that is already infatuated."
    /// </summary>
    [Test]
    public void OnSecondaryEffect_TargetAlreadyInfatuated_Fails()
    {
        // Arrange
        var (script, move, target, targetVolatile, hitData) = CreateTestSetup(Gender.Male, Gender.Female);
        targetVolatile.Contains(new StringKey("infatuated")).Returns(true);

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

        // Assert
        hitData.Received(1).Fail();
        targetVolatile.DidNotReceive().Add(Arg.Any<Script>());
    }
}