using System.Net;
using System.Net.Http.Json;
using System.Text;
using NeonSprawl.Server.Game.AbilityInput;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.AbilityInput;

public sealed class HotbarLoadoutApiTests
{
    [Fact]
    public async Task GetHotbarLoadout_ShouldReturnEightSlots_WhenPlayerKnown()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.GetAsync("/game/players/dev-local-1/hotbar-loadout");

        // Assert
        var body = await response.Content.ReadFromJsonAsync<HotbarLoadoutResponse>();
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        Assert.NotNull(body);
        Assert.Equal(HotbarLoadoutResponse.CurrentSchemaVersion, body!.SchemaVersion);
        Assert.Equal(HotbarLoadoutResponse.SlotCountV1, body.SlotCount);
        Assert.Equal(HotbarLoadoutResponse.SlotCountV1, body.Slots.Count);
        Assert.All(body.Slots, slot => Assert.Null(slot.AbilityId));
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldPersistBindings_AndRoundTripInGet()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        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 },
                    new HotbarSlotBindingJson { SlotIndex = 3, AbilityId = PrototypeAbilityRegistry.PrototypeBurst },
                ],
            });

        // Assert
        var postBody = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
        var get = await client.GetFromJsonAsync<HotbarLoadoutResponse>("/game/players/dev-local-1/hotbar-loadout");
        Assert.Equal(HttpStatusCode.OK, post.StatusCode);
        Assert.NotNull(postBody);
        Assert.True(postBody!.Updated);
        Assert.Null(postBody.ReasonCode);
        Assert.Equal(PrototypeAbilityRegistry.PrototypePulse, postBody.Loadout.Slots[0].AbilityId);
        Assert.Equal(PrototypeAbilityRegistry.PrototypeBurst, postBody.Loadout.Slots[3].AbilityId);
        Assert.NotNull(get);
        Assert.Equal(PrototypeAbilityRegistry.PrototypePulse, get!.Slots[0].AbilityId);
        Assert.Equal(PrototypeAbilityRegistry.PrototypeBurst, get.Slots[3].AbilityId);
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldDenyOutOfBounds_WithReasonCode()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/hotbar-loadout",
            new HotbarLoadoutUpdateRequest
            {
                SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
                Slots = [new HotbarSlotBindingJson { SlotIndex = 8, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
            });

        // Assert
        var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
        Assert.Equal(HttpStatusCode.OK, post.StatusCode);
        Assert.NotNull(body);
        Assert.False(body!.Updated);
        Assert.Equal(HotbarLoadoutApi.ReasonSlotOutOfBounds, body.ReasonCode);
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldDenyNegativeSlot_WithReasonCode()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/hotbar-loadout",
            new HotbarLoadoutUpdateRequest
            {
                SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
                Slots = [new HotbarSlotBindingJson { SlotIndex = -1, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
            });

        // Assert
        var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
        Assert.Equal(HttpStatusCode.OK, post.StatusCode);
        Assert.NotNull(body);
        Assert.False(body!.Updated);
        Assert.Equal(HotbarLoadoutApi.ReasonSlotOutOfBounds, body.ReasonCode);
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldDenyUnknownAbility_WithReasonCode()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/hotbar-loadout",
            new HotbarLoadoutUpdateRequest
            {
                SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
                Slots = [new HotbarSlotBindingJson { SlotIndex = 2, AbilityId = "missing_ability" }],
            });

        // Assert
        var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
        Assert.Equal(HttpStatusCode.OK, post.StatusCode);
        Assert.NotNull(body);
        Assert.False(body!.Updated);
        Assert.Equal(HotbarLoadoutApi.ReasonUnknownAbility, body.ReasonCode);
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldDenyDuplicateSlots_WithReasonCode()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/hotbar-loadout",
            new HotbarLoadoutUpdateRequest
            {
                SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
                Slots =
                [
                    new HotbarSlotBindingJson { SlotIndex = 1, AbilityId = PrototypeAbilityRegistry.PrototypePulse },
                    new HotbarSlotBindingJson { SlotIndex = 1, AbilityId = PrototypeAbilityRegistry.PrototypeGuard },
                ],
            });

        // Assert
        var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
        Assert.Equal(HttpStatusCode.OK, post.StatusCode);
        Assert.NotNull(body);
        Assert.False(body!.Updated);
        Assert.Equal(HotbarLoadoutApi.ReasonDuplicateSlot, body.ReasonCode);
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldNormalizeAbilityIdCaseAndWhitespace_WhenKnownAbility()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/hotbar-loadout",
            new HotbarLoadoutUpdateRequest
            {
                SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
                Slots = [new HotbarSlotBindingJson { SlotIndex = 5, AbilityId = "  PROTOTYPE_DASH  " }],
            });

        // Assert
        var body = await post.Content.ReadFromJsonAsync<HotbarLoadoutUpdateResponse>();
        Assert.Equal(HttpStatusCode.OK, post.StatusCode);
        Assert.NotNull(body);
        Assert.True(body!.Updated);
        Assert.Equal(PrototypeAbilityRegistry.PrototypeDash, body.Loadout.Slots[5].AbilityId);
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldReturnBadRequest_WhenSchemaVersionWrong()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/hotbar-loadout",
            new HotbarLoadoutUpdateRequest
            {
                SchemaVersion = 999,
                Slots = [new HotbarSlotBindingJson { SlotIndex = 0, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
            });

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, post.StatusCode);
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldReturnBadRequest_WhenBodyMalformed()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.PostAsync(
            "/game/players/dev-local-1/hotbar-loadout",
            new StringContent("{\"schemaVersion\":1,\"slots\":[", Encoding.UTF8, "application/json"));

        // Assert
        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
    }

    [Fact]
    public async Task GetHotbarLoadout_ShouldReturnNotFound_WhenPlayerUnknown()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var response = await client.GetAsync("/game/players/missing-player/hotbar-loadout");

        // Assert
        Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
    }

    [Fact]
    public async Task PostHotbarLoadout_ShouldReturnNotFound_WhenPlayerUnknown()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();

        // Act
        var post = await client.PostAsJsonAsync(
            "/game/players/missing-player/hotbar-loadout",
            new HotbarLoadoutUpdateRequest
            {
                SchemaVersion = HotbarLoadoutUpdateRequest.CurrentSchemaVersion,
                Slots = [new HotbarSlotBindingJson { SlotIndex = 0, AbilityId = PrototypeAbilityRegistry.PrototypePulse }],
            });

        // Assert
        Assert.Equal(HttpStatusCode.NotFound, post.StatusCode);
    }
}
