using System.Linq;
using System.Net;
using System.Net.Http.Json;
using NeonSprawl.Server.Game.AbilityInput;
using NeonSprawl.Server.Game.Combat;
using NeonSprawl.Server.Game.Targeting;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.Combat;

public class CombatTargetsWorldApiTests
{
    /// <summary>Prototype combat targets in registry id order (ordinal). Keep in sync with Bruno.</summary>
    public static readonly string[] PrototypeTargetsInIdOrder =
    [
        PrototypeTargetRegistry.PrototypeTargetAlphaId,
        PrototypeTargetRegistry.PrototypeTargetBetaId,
    ];

    private static async Task BindSlot0PulseAsync(HttpClient client)
    {
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/hotbar-loadout",
            new HotbarLoadoutUpdateRequest
            {
                SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
                Slots = [new HotbarSlotBindingJson { SlotIndex = 0, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
            });
        post.EnsureSuccessStatusCode();
    }

    private static async Task LockPrototypeTargetAlphaAsync(HttpClient client)
    {
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/target/select",
            new TargetSelectRequest
            {
                SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
                TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
            });
        response.EnsureSuccessStatusCode();
        var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
        Assert.NotNull(body);
        Assert.True(body!.SelectionApplied);
    }

    private static AbilityCastRequest PulseCastRequest() =>
        new()
        {
            SchemaVersion = AbilityCastRequest.CurrentSchemaVersion,
            SlotIndex = 0,
            AbilityId = PrototypeAbilityRegistry.PrototypePulse,
            TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
        };

    [Fact]
    public async Task GetCombatTargets_ShouldReturnSchemaV1_WithBothTargetsAtFullHp()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        // Act
        var response = await client.GetAsync("/game/world/combat-targets");
        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<CombatTargetsListResponse>();
        Assert.NotNull(body);
        Assert.Equal(CombatTargetsListResponse.CurrentSchemaVersion, body!.SchemaVersion);
        Assert.NotNull(body.Targets);
        Assert.Equal(2, body.Targets.Count);
        var ids = body.Targets.Select(static t => t.TargetId).ToList();
        Assert.Equal(PrototypeTargetsInIdOrder, ids);

        var alpha = body.Targets.Single(t => t.TargetId == PrototypeTargetRegistry.PrototypeTargetAlphaId);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, alpha.MaxHp);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, alpha.CurrentHp);
        Assert.False(alpha.Defeated);

        var beta = body.Targets.Single(t => t.TargetId == PrototypeTargetRegistry.PrototypeTargetBetaId);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, beta.MaxHp);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, beta.CurrentHp);
        Assert.False(beta.Defeated);
    }

    [Fact]
    public async Task GetCombatTargets_ShouldReflectCastDamage_AfterOnePulseOnAlpha()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        await BindSlot0PulseAsync(client);
        await LockPrototypeTargetAlphaAsync(client);
        var castResponse = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/ability-cast",
            PulseCastRequest());
        castResponse.EnsureSuccessStatusCode();
        var castBody = await castResponse.Content.ReadFromJsonAsync<AbilityCastResponse>();

        // Act
        var response = await client.GetAsync("/game/world/combat-targets");
        var body = await response.Content.ReadFromJsonAsync<CombatTargetsListResponse>();

        // Assert
        Assert.NotNull(castBody);
        Assert.True(castBody!.Accepted);
        Assert.NotNull(castBody.CombatResolution);
        Assert.Equal(75, castBody.CombatResolution!.TargetRemainingHp);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotNull(body);
        var alpha = body!.Targets.Single(t => t.TargetId == PrototypeTargetRegistry.PrototypeTargetAlphaId);
        Assert.Equal(75, alpha.CurrentHp);
        Assert.False(alpha.Defeated);
        var beta = body.Targets.Single(t => t.TargetId == PrototypeTargetRegistry.PrototypeTargetBetaId);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, beta.CurrentHp);
        Assert.False(beta.Defeated);
    }

    [Fact]
    public async Task GetCombatTargets_ShouldShowDefeated_AfterFourPulsesOnAlpha()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        Assert.NotNull(factory.FakeClock);
        await BindSlot0PulseAsync(client);
        await LockPrototypeTargetAlphaAsync(client);
        var cast = PulseCastRequest();
        for (var i = 0; i < 4; i++)
        {
            var castResponse = await client.PostAsJsonAsync("/game/players/dev-local-1/ability-cast", cast);
            castResponse.EnsureSuccessStatusCode();
            if (i < 3)
            {
                factory.FakeClock!.Advance(TimeSpan.FromSeconds(3) + TimeSpan.FromMilliseconds(50));
            }
        }

        // Act
        var response = await client.GetAsync("/game/world/combat-targets");

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<CombatTargetsListResponse>();
        Assert.NotNull(body);
        var alpha = body!.Targets.Single(t => t.TargetId == PrototypeTargetRegistry.PrototypeTargetAlphaId);
        Assert.Equal(0, alpha.CurrentHp);
        Assert.True(alpha.Defeated);
        var beta = body.Targets.Single(t => t.TargetId == PrototypeTargetRegistry.PrototypeTargetBetaId);
        Assert.Equal(PrototypeCombatConstants.MaxPrototypeTargetHp, beta.CurrentHp);
        Assert.False(beta.Defeated);
    }
}
