using Microsoft.Extensions.DependencyInjection;
using NeonSprawl.Server.Game.AbilityInput;
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 CombatOperationsTests
{
    private static AbilityDefinitionRegistry CreatePrototypeRegistry()
    {
        var rows = new Dictionary<string, AbilityDefRow>(StringComparer.Ordinal)
        {
            [PrototypeAbilityRegistry.PrototypeBurst] = new AbilityDefRow(
                PrototypeAbilityRegistry.PrototypeBurst,
                "Prototype Burst",
                40,
                5.0,
                "attack"),
            [PrototypeAbilityRegistry.PrototypeDash] = new AbilityDefRow(
                PrototypeAbilityRegistry.PrototypeDash,
                "Prototype Dash",
                0,
                4.0,
                "movement"),
            [PrototypeAbilityRegistry.PrototypeGuard] = new AbilityDefRow(
                PrototypeAbilityRegistry.PrototypeGuard,
                "Prototype Guard",
                0,
                6.0,
                "utility"),
            [PrototypeAbilityRegistry.PrototypePulse] = new AbilityDefRow(
                PrototypeAbilityRegistry.PrototypePulse,
                "Prototype Pulse",
                25,
                3.0,
                "attack"),
        };
        var catalog = new AbilityDefinitionCatalog("/tmp/abilities", rows, catalogJsonFileCount: 1);
        return new AbilityDefinitionRegistry(catalog);
    }

    [Fact]
    public void TryResolve_WithPulseOnFreshAlpha_ShouldDealCatalogDamage()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypePulse,
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);
        // Assert
        Assert.True(result.Success);
        Assert.Null(result.ReasonCode);
        Assert.Equal(25, result.DamageDealt);
        Assert.Equal(75, result.TargetRemainingHp);
        Assert.False(result.TargetDefeated);
    }

    [Fact]
    public void TryResolve_WithFourPulses_ShouldDefeatTargetOnFourthResolve()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        for (var i = 0; i < 3; i++)
        {
            _ = CombatOperations.TryResolve(
                PrototypeAbilityRegistry.PrototypePulse,
                PrototypeTargetRegistry.PrototypeTargetAlphaId,
                registry,
                store);
        }

        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypePulse,
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);

        // Assert
        Assert.True(result.Success);
        Assert.Equal(0, result.TargetRemainingHp);
        Assert.True(result.TargetDefeated);
    }

    [Fact]
    public void TryResolve_OnDefeatedTarget_ShouldDenyWithTargetDefeated()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        for (var i = 0; i < 4; i++)
        {
            _ = CombatOperations.TryResolve(
                PrototypeAbilityRegistry.PrototypePulse,
                PrototypeTargetRegistry.PrototypeTargetAlphaId,
                registry,
                store);
        }

        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypePulse,
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);
        store.TryGet(PrototypeTargetRegistry.PrototypeTargetAlphaId, out var snapshot);

        // Assert
        Assert.False(result.Success);
        Assert.Equal(CombatReasonCodes.TargetDefeated, result.ReasonCode);
        Assert.Equal(0, result.DamageDealt);
        Assert.Equal(0, result.TargetRemainingHp);
        Assert.False(result.TargetDefeated);
        Assert.Equal(0, snapshot.CurrentHp);
        Assert.True(snapshot.Defeated);
    }

    [Fact]
    public void TryResolve_WithGuardOnLiveTarget_ShouldSucceedWithZeroDamage()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypeGuard,
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);
        store.TryGet(PrototypeTargetRegistry.PrototypeTargetAlphaId, out var snapshot);

        // Assert
        Assert.True(result.Success);
        Assert.Null(result.ReasonCode);
        Assert.Equal(0, result.DamageDealt);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, result.TargetRemainingHp);
        Assert.False(result.TargetDefeated);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, snapshot.CurrentHp);
    }

    [Fact]
    public void TryResolve_WithGuardOnDefeatedTarget_ShouldDenyWithTargetDefeated()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        _ = store.TryApplyDamage(PrototypeTargetRegistry.PrototypeTargetAlphaId, 100, out _);
        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypeGuard,
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);
        // Assert
        Assert.False(result.Success);
        Assert.Equal(CombatReasonCodes.TargetDefeated, result.ReasonCode);
        Assert.Equal(0, result.DamageDealt);
        Assert.Equal(0, result.TargetRemainingHp);
        Assert.False(result.TargetDefeated);
    }

    [Fact]
    public void TryResolve_WithDashOnLiveTarget_ShouldSucceedWithZeroDamage()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypeDash,
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);
        // Assert
        Assert.True(result.Success);
        Assert.Null(result.ReasonCode);
        Assert.Equal(0, result.DamageDealt);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, result.TargetRemainingHp);
        Assert.False(result.TargetDefeated);
    }

    [Fact]
    public void TryResolve_WithUnknownAbility_ShouldDenyWithUnknownAbility()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var result = CombatOperations.TryResolve(
            "not_a_real_ability",
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);
        // Assert
        Assert.False(result.Success);
        Assert.Equal(CombatReasonCodes.UnknownAbility, result.ReasonCode);
        Assert.Equal(0, result.DamageDealt);
        Assert.Null(result.TargetRemainingHp);
    }

    [Fact]
    public void TryResolve_WithUnknownTarget_ShouldDenyWithUnknownTarget()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypePulse,
            "not_a_target",
            registry,
            store);
        // Assert
        Assert.False(result.Success);
        Assert.Equal(CombatReasonCodes.UnknownTarget, result.ReasonCode);
        Assert.Equal(0, result.DamageDealt);
        Assert.Null(result.TargetRemainingHp);
    }

    [Fact]
    public void TryResolve_WithBurstOnFreshAlpha_ShouldDealFortyDamage()
    {
        // Arrange
        var registry = CreatePrototypeRegistry();
        var store = new InMemoryCombatEntityHealthStore();
        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypeBurst,
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);
        // Assert
        Assert.True(result.Success);
        Assert.Equal(40, result.DamageDealt);
        Assert.Equal(60, result.TargetRemainingHp);
        Assert.False(result.TargetDefeated);
    }

    [Fact]
    public async Task Host_ShouldResolveCombatDependenciesFromDi_WhenStartupSucceeds()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        using var client = factory.CreateClient();
        _ = await client.GetAsync("/health");
        var registry = factory.Services.GetRequiredService<IAbilityDefinitionRegistry>();
        var store = factory.Services.GetRequiredService<ICombatEntityHealthStore>();
        // Act
        var result = CombatOperations.TryResolve(
            PrototypeAbilityRegistry.PrototypePulse,
            PrototypeTargetRegistry.PrototypeTargetAlphaId,
            registry,
            store);
        // Assert
        Assert.True(result.Success);
        Assert.Equal(25, result.DamageDealt);
        Assert.Equal(75, result.TargetRemainingHp);
    }
}
