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

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

/// <summary>
/// Tests for the <see cref="Explosion"/> move script.
/// Gen VII Bulbapedia behavior: "the user faints upon using this move", and from Generation VI onwards
/// "Explosion returns to damaging the target before the user faints."
/// </summary>
public class ExplosionTests
{
    private static (Explosion script, IExecutingMove move, IPokemon user) CreateTestSetup(uint currentHealth)
    {
        var script = new Explosion();
        var move = Substitute.For<IExecutingMove>();
        var user = Substitute.For<IPokemon>();
        user.CurrentHealth.Returns(currentHealth);
        move.User.Returns(user);
        return (script, move, user);
    }

    /// <summary>
    /// Helper to extract the damage amount from the user's received Damage calls.
    /// </summary>
    private static uint? GetDamageAmount(IPokemon user)
    {
        var call = user.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "Damage");
        return call != null ? (uint)call.GetArguments()[0]! : null;
    }

    /// <summary>
    /// Bulbapedia: "the user faints upon using this move" — after the hits are dealt, the user takes at
    /// least its full current HP in damage, guaranteeing it faints.
    /// </summary>
    [Test]
    public async Task OnAfterHits_UserTakesAtLeastItsCurrentHealthInDamage()
    {
        // Arrange
        var (script, move, user) = CreateTestSetup(100);

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

        // Assert
        var damage = GetDamageAmount(user);
        await Assert.That(damage).IsNotNull();
        await Assert.That(damage!.Value).IsGreaterThanOrEqualTo(100u);
    }

    /// <summary>
    /// Bulbapedia: "Sturdy, Focus Band, and Focus Sash do not prevent user fainting." The self-inflicted
    /// faint is indirect damage (<see cref="DamageSource.Misc"/>), not move damage, so hold-on effects
    /// that watch for move damage do not apply.
    /// </summary>
    [Test]
    public async Task OnAfterHits_UsesMiscDamageSource()
    {
        // Arrange
        var (script, move, user) = CreateTestSetup(100);

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

        // Assert
        var call = user.ReceivedCalls().First(c => c.GetMethodInfo().Name == "Damage");
        await Assert.That((DamageSource)call.GetArguments()[1]!).IsEqualTo(DamageSource.Misc);
    }
}