using System.Linq;
using System.Net;
using System.Net.Http.Json;
using NeonSprawl.Server.Game.Items;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.Items;

/// <summary>Regression tests for <c>GET</c>/<c>POST …/inventory</c> (NEO-55).</summary>
public sealed class PlayerInventoryApiTests
{
    private static PlayerInventoryMutationRequest Mutation(
        string mutationKind,
        string itemId,
        int quantity,
        int schemaVersion = PlayerInventoryMutationRequest.CurrentSchemaVersion) =>
        new()
        {
            SchemaVersion = schemaVersion,
            MutationKind = mutationKind,
            ItemId = itemId,
            Quantity = quantity,
        };

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

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

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

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

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

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
        Assert.NotNull(body);
        Assert.Equal(PlayerInventorySnapshotResponse.CurrentSchemaVersion, body!.SchemaVersion);
        Assert.Equal("dev-local-1", body.PlayerId);
        Assert.Equal(PlayerInventorySnapshot.BagSlotCount, body.BagSlots.Count);
        Assert.Single(body.EquipmentSlots);
        Assert.All(body.BagSlots, static slot =>
        {
            Assert.Null(slot.ItemId);
            Assert.Equal(0, slot.Quantity);
        });
        Assert.All(body.EquipmentSlots, static slot =>
        {
            Assert.Null(slot.ItemId);
            Assert.Equal(0, slot.Quantity);
        });
        Assert.Equal(Enumerable.Range(0, PlayerInventorySnapshot.BagSlotCount), body.BagSlots.Select(static s => s.SlotIndex));
        Assert.Equal(0, body.EquipmentSlots[0].SlotIndex);
    }

    [Fact]
    public async Task PostInventory_ShouldReturnBadRequest_WhenSchemaVersionMismatch()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var request = Mutation("add", "scrap_metal_bulk", quantity: 1, schemaVersion: 99);

        // Act
        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/inventory", request);

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

    [Fact]
    public async Task PostInventory_ShouldReturnBadRequest_WhenMutationKindUnknown()
    {
        // Arrange
        await using var factory = new InMemoryWebApplicationFactory();
        var client = factory.CreateClient();
        var request = Mutation("consume", "scrap_metal_bulk", quantity: 1);

        // Act
        var response = await client.PostAsJsonAsync("/game/players/dev-local-1/inventory", request);

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

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

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/missing-player/inventory",
            Mutation("add", "scrap_metal_bulk", quantity: 1));

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

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

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/inventory",
            Mutation("add", "scrap_metal_bulk", quantity: 10));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
        Assert.NotNull(body);
        Assert.True(body!.Applied);
        Assert.Null(body.ReasonCode);
        Assert.Equal("dev-local-1", body.Inventory.PlayerId);
        var occupied = body.Inventory.BagSlots.Where(static s => s.Quantity > 0).ToList();
        Assert.Single(occupied);
        Assert.Equal("scrap_metal_bulk", occupied[0].ItemId);
        Assert.Equal(10, occupied[0].Quantity);
    }

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

        // Act
        var addResponse = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/inventory",
            Mutation("add", "scrap_metal_bulk", quantity: 10));
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/inventory",
            Mutation("remove", "scrap_metal_bulk", quantity: 4));

        // Assert
        Assert.Equal(HttpStatusCode.OK, addResponse.StatusCode);
        var addBody = await addResponse.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
        Assert.NotNull(addBody);
        Assert.True(addBody!.Applied);
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
        Assert.NotNull(body);
        Assert.True(body!.Applied);
        Assert.Null(body.ReasonCode);
        var occupied = body.Inventory.BagSlots.Where(static s => s.Quantity > 0).ToList();
        Assert.Single(occupied);
        Assert.Equal("scrap_metal_bulk", occupied[0].ItemId);
        Assert.Equal(6, occupied[0].Quantity);
    }

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

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/inventory",
            Mutation("add", "scrap_metal_bulk", quantity: 0));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
        Assert.NotNull(body);
        Assert.False(body!.Applied);
        Assert.Equal(PlayerInventoryReasonCodes.InvalidQuantity, body.ReasonCode);
        Assert.All(body.Inventory.BagSlots, static slot => Assert.Equal(0, slot.Quantity));
    }

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

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/inventory",
            Mutation("add", "not-a-prototype-item", quantity: 1));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
        Assert.NotNull(body);
        Assert.False(body!.Applied);
        Assert.Equal(PlayerInventoryReasonCodes.InvalidItem, body.ReasonCode);
        Assert.All(body.Inventory.BagSlots, static slot => Assert.Equal(0, slot.Quantity));
    }

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

        // Act
        var response = await client.PostAsJsonAsync(
            "/game/players/dev-local-1/inventory",
            Mutation("remove", "scrap_metal_bulk", quantity: 1));

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
        Assert.NotNull(body);
        Assert.False(body!.Applied);
        Assert.Equal(PlayerInventoryReasonCodes.InsufficientQuantity, body.ReasonCode);
    }
}
