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

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

/// <summary>
/// Tests for the <see cref="Embargo"/> move script and its <see cref="EmbargoEffect"/>.
/// Gen VII Bulbapedia behavior: "For five turns, the target's held item has its effects negated and its
/// Trainer cannot use items from the Bag on it."
/// </summary>
public class EmbargoTests
{
    /// <summary>
    /// Creates a target Pokémon whose volatile scripts are a real <see cref="ScriptSet"/>.
    /// </summary>
    private static (IPokemon target, IScriptSet targetVolatile) CreateTarget()
    {
        var target = Substitute.For<IPokemon>();
        target.GetScripts().Returns(_ => new ScriptIterator(Array.Empty<IEnumerable<ScriptContainer>>()));
        IScriptSet targetVolatile = new ScriptSet(target);
        target.Volatile.Returns(targetVolatile);
        return (target, targetVolatile);
    }

    /// <summary>
    /// Bulbapedia: "For five turns, the target's held item has its effects negated" — using the move
    /// attaches the <see cref="EmbargoEffect"/> to the target.
    /// </summary>
    [Test]
    public async Task OnSecondaryEffect_AddsEmbargoEffectToTarget()
    {
        // Arrange
        var script = new Embargo();
        var move = Substitute.For<IExecutingMove>();
        var (target, targetVolatile) = CreateTarget();

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

        // Assert
        await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EmbargoEffect>())).IsTrue();
    }

    /// <summary>
    /// Bulbapedia: the target's "Trainer cannot use items from the Bag on it" and its held item is
    /// negated — the effect prevents held item consumption.
    /// </summary>
    [Test]
    public async Task PreventHeldItemConsume_EffectActive_ConsumptionPrevented()
    {
        // Arrange
        var effect = new EmbargoEffect();
        var prevented = false;

        // Act
        effect.PreventHeldItemConsume(Substitute.For<IPokemon>(), Substitute.For<IItem>(), ref prevented);

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

    /// <summary>
    /// Bulbapedia: the effect lasts "for five turns" — after four end-of-turn ticks it is still active.
    /// </summary>
    [Test]
    public async Task OnEndTurn_FourTurnsPassed_EffectStillActive()
    {
        // Arrange
        var (target, targetVolatile) = CreateTarget();
        var effect = new EmbargoEffect();
        targetVolatile.Add(effect);

        // Act
        for (var i = 0; i < 4; i++)
            effect.OnEndTurn(target, Substitute.For<IBattle>());

        // Assert
        await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EmbargoEffect>())).IsTrue();
    }

    /// <summary>
    /// Bulbapedia: the effect lasts "for five turns" — after the fifth end-of-turn tick it removes itself.
    /// </summary>
    [Test]
    public async Task OnEndTurn_FiveTurnsPassed_EffectRemovesItself()
    {
        // Arrange
        var (target, targetVolatile) = CreateTarget();
        var effect = new EmbargoEffect();
        targetVolatile.Add(effect);

        // Act
        for (var i = 0; i < 5; i++)
            effect.OnEndTurn(target, Substitute.For<IBattle>());

        // Assert
        await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EmbargoEffect>())).IsFalse();
    }

    /// <summary>
    /// Bulbapedia: "For five turns" — using Embargo again on an already affected target restarts the
    /// five turn duration (the effect stacks by resetting its counter).
    /// </summary>
    [Test]
    public async Task Stack_UsedAgain_DurationResetsToFiveTurns()
    {
        // Arrange
        var (target, targetVolatile) = CreateTarget();
        var effect = new EmbargoEffect();
        targetVolatile.Add(effect);
        for (var i = 0; i < 3; i++)
            effect.OnEndTurn(target, Substitute.For<IBattle>());

        // Act - Embargo is used again, restarting the duration
        effect.Stack();
        for (var i = 0; i < 4; i++)
            effect.OnEndTurn(target, Substitute.For<IBattle>());

        // Assert - only four of the renewed five turns have passed
        await Assert.That(targetVolatile.Contains(ScriptUtils.ResolveName<EmbargoEffect>())).IsTrue();
    }
}