using Microsoft.Extensions.DependencyInjection;
using NeonSprawl.Server.Game.Combat;
using NeonSprawl.Server.Game.Targeting;
using NeonSprawl.Server.Tests;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.Combat;

public sealed class CombatEntityHealthStoreTests
{
    [Fact]
    public void TryGet_OnFirstAccess_ShouldLazyInitAlphaToFullHp()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var found = store.TryGet(PrototypeTargetRegistry.PrototypeTargetAlphaId, out var snapshot);
        // Assert
        Assert.True(found);
        Assert.Equal(PrototypeTargetRegistry.PrototypeTargetAlphaId, snapshot.TargetId);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, snapshot.MaxHp);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, snapshot.CurrentHp);
        Assert.False(snapshot.Defeated);
    }

    [Fact]
    public void TryApplyDamage_ShouldReduceHp_WhenDamageIsApplied()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var applied = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 25, out var snapshot);
        // Assert
        Assert.True(applied);
        Assert.Equal(75, snapshot.CurrentHp);
        Assert.False(snapshot.Defeated);
    }

    [Fact]
    public void TryApplyDamage_RepeatedUntilZero_ShouldMarkDefeated()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var first = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 25, out _);
        var second = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 25, out _);
        var third = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 25, out _);
        var fourth = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 25, out var defeated);
        // Assert
        Assert.True(first);
        Assert.True(second);
        Assert.True(third);
        Assert.True(fourth);
        Assert.Equal(0, defeated.CurrentHp);
        Assert.True(defeated.Defeated);
    }

    [Fact]
    public void TryApplyDamage_OnDefeatedTarget_ShouldKeepHpAtZeroAndRemainDefeated()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        _ = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 100, out _);
        // Act
        var overkill = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 25, out var snapshot);
        // Assert
        Assert.True(overkill);
        Assert.Equal(0, snapshot.CurrentHp);
        Assert.True(snapshot.Defeated);
    }

    [Fact]
    public void TryResetToFull_AfterDefeat_ShouldRestoreFullHpAndClearDefeated()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        _ = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 100, out _);
        // Act
        var reset = store.TryResetToFull(PrototypeTargetRegistry.PrototypeTargetAlphaId, out var snapshot);
        // Assert
        Assert.True(reset);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, snapshot.CurrentHp);
        Assert.False(snapshot.Defeated);
    }

    [Fact]
    public void TryGet_OnBeta_ShouldLazyInitIndependentlyFromAlpha()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        _ = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 40, out var alphaAfterDamage);
        // Act
        var found = store.TryGet(PrototypeTargetRegistry.PrototypeTargetBetaId, out var beta);
        // Assert
        Assert.True(found);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, beta.CurrentHp);
        Assert.False(beta.Defeated);
        Assert.Equal(60, alphaAfterDamage.CurrentHp);
    }

    [Fact]
    public void TryGet_ShouldReturnFalse_WhenTargetIdIsUnknown()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var found = store.TryGet("not_a_prototype_target", out var snapshot);
        // Assert
        Assert.False(found);
        Assert.Equal(default, snapshot);
    }

    [Fact]
    public void TryGet_ShouldReturnFalse_WhenTargetIdIsEmpty()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var found = store.TryGet("   ", out var snapshot);
        // Assert
        Assert.False(found);
        Assert.Equal(default, snapshot);
    }

    [Fact]
    public void TryApplyDamage_ShouldReturnFalse_WhenDamageIsNegative()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var applied = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, -1, out var snapshot);
        // Assert
        Assert.False(applied);
        Assert.Equal(default, snapshot);
    }

    [Fact]
    public void TryApplyDamage_ShouldReturnFalse_WhenTargetIdIsUnknown()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var applied = store.TryApplyDamage("not_a_prototype_target", 25, out var snapshot);
        // Assert
        Assert.False(applied);
        Assert.Equal(default, snapshot);
    }

    [Fact]
    public void TryApplyDamage_ShouldReturnFalse_WhenTargetIdIsEmpty()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var applied = store.TryApplyDamage("   ", 25, out var snapshot);
        // Assert
        Assert.False(applied);
        Assert.Equal(default, snapshot);
    }

    [Fact]
    public void TryGet_ShouldNormalizeCaseVariantTargetIds()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var found = store.TryGet(" Prototype_Target_Alpha ", out var snapshot);
        // Assert
        Assert.True(found);
        Assert.Equal(PrototypeTargetRegistry.PrototypeTargetAlphaId, snapshot.TargetId);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, snapshot.CurrentHp);
    }

    [Fact]
    public void TryResetToFull_ShouldReturnFalse_WhenTargetIdIsUnknown()
    {
        // Arrange
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var reset = store.TryResetToFull("not_a_prototype_target", out var snapshot);
        // Assert
        Assert.False(reset);
        Assert.Equal(default, snapshot);
    }

    [Fact]
    public async Task Host_ShouldResolveCombatEntityHealthStoreFromDi_WhenStartupSucceeds()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        using var client = factory.CreateClient();
        _ = await client.GetAsync("/health");
        // Act
        var store = factory.Services.GetRequiredService<ICombatEntityHealthStore>();
        var found = store.TryGet(PrototypeTargetRegistry.PrototypeTargetAlphaId, out var snapshot);
        // Assert
        Assert.True(found);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, snapshot.CurrentHp);
        Assert.False(snapshot.Defeated);
    }
}
