using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NeonSprawl.Server.Game.Items;
using NeonSprawl.Server.Game.PositionState;
using NeonSprawl.Server.Tests.Game.PositionState;
using Npgsql;
using Xunit;

namespace NeonSprawl.Server.Tests.Game.Items;

[Collection("Postgres integration")]
public sealed class PlayerInventoryPersistenceIntegrationTests(PostgresIntegrationHarness harness)
{
    private PostgresWebApplicationFactory Factory => harness.Factory;

    [RequirePostgresFact]
    public async Task TryAddStackAcrossNewFactory_ShouldPersistSnapshot()
    {
        // Arrange
        await ResetInventoryTableAsync();
        PlayerInventoryMutationOutcome addOutcome;

        // Act — write through first host
        using (var firstScope = Factory.Services.CreateScope())
        {
            var registry = firstScope.ServiceProvider.GetRequiredService<IItemDefinitionRegistry>();
            var store = firstScope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
            addOutcome = PlayerInventoryOperations.TryAddStack(
                "dev-local-1",
                "scrap_metal_bulk",
                quantity: 25,
                registry,
                store);
        }

        // Act — read back through a fresh host
        await using var secondFactory = new PostgresWebApplicationFactory();
        using var secondScope = secondFactory.Services.CreateScope();
        var readStore = secondScope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
        var readBack = readStore.TryGetSnapshot("dev-local-1", out var snapshot);

        // Assert
        Assert.Equal(PlayerInventoryMutationKind.Applied, addOutcome.Kind);
        Assert.True(readBack);
        Assert.NotNull(snapshot);
        Assert.Equal("scrap_metal_bulk", snapshot!.BagSlots[0].ItemId);
        Assert.Equal(25, snapshot.BagSlots[0].Quantity);
    }

    [RequirePostgresFact]
    public async Task TryAddStack_WhenDenied_ShouldNotPersistPartialState()
    {
        // Arrange
        await ResetInventoryTableAsync();
        using (var seedScope = Factory.Services.CreateScope())
        {
            var registry = seedScope.ServiceProvider.GetRequiredService<IItemDefinitionRegistry>();
            var store = seedScope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
            for (var i = 0; i < PlayerInventorySnapshot.BagSlotCount; i++)
            {
                _ = PlayerInventoryOperations.TryAddStack(
                    "dev-local-1",
                    "survey_drone_kit",
                    quantity: 1,
                    registry,
                    store);
            }
        }

        // Act
        PlayerInventoryMutationOutcome denyOutcome;
        using (var scope = Factory.Services.CreateScope())
        {
            var registry = scope.ServiceProvider.GetRequiredService<IItemDefinitionRegistry>();
            var store = scope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
            denyOutcome = PlayerInventoryOperations.TryAddStack(
                "dev-local-1",
                "scrap_metal_bulk",
                quantity: 1,
                registry,
                store);
        }

        // Act — verify on fresh host
        await using var secondFactory = new PostgresWebApplicationFactory();
        using var secondScope = secondFactory.Services.CreateScope();
        var readStore = secondScope.ServiceProvider.GetRequiredService<IPlayerInventoryStore>();
        readStore.TryGetSnapshot("dev-local-1", out var persisted);

        // Assert
        Assert.Equal(PlayerInventoryMutationKind.Denied, denyOutcome.Kind);
        Assert.Equal(PlayerInventoryReasonCodes.InventoryFull, denyOutcome.ReasonCode);
        Assert.Equal(PlayerInventorySnapshot.BagSlotCount, OccupiedBagSlotCount(persisted!));
    }

    private async Task ResetInventoryTableAsync()
    {
        var cs = Environment.GetEnvironmentVariable("ConnectionStrings__NeonSprawl");
        if (string.IsNullOrWhiteSpace(cs))
        {
            throw new InvalidOperationException("ConnectionStrings__NeonSprawl is not set.");
        }

        _ = Factory.Services;
        var options = Factory.Services.GetRequiredService<IOptions<GamePositionOptions>>().Value;

        var positionDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V001__player_position.sql");
        var inventoryDdlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V005__player_inventory.sql");
        if (!File.Exists(positionDdlPath))
        {
            throw new FileNotFoundException($"Test DDL not found at '{positionDdlPath}'.", positionDdlPath);
        }

        if (!File.Exists(inventoryDdlPath))
        {
            throw new FileNotFoundException($"Test DDL not found at '{inventoryDdlPath}'.", inventoryDdlPath);
        }

        var positionDdl = await File.ReadAllTextAsync(positionDdlPath);
        var inventoryDdl = await File.ReadAllTextAsync(inventoryDdlPath);
        await using var conn = new NpgsqlConnection(cs);
        await conn.OpenAsync();
        await using (var applyPosition = new NpgsqlCommand(positionDdl, conn))
        {
            await applyPosition.ExecuteNonQueryAsync();
        }

        await using (var truncate = new NpgsqlCommand("TRUNCATE player_position CASCADE;", conn))
        {
            await truncate.ExecuteNonQueryAsync();
        }

        PostgresPositionBootstrap.SeedDevPlayer(conn, options);

        await using (var applyInventory = new NpgsqlCommand(inventoryDdl, conn))
        {
            await applyInventory.ExecuteNonQueryAsync();
        }

        await using (var truncateInventory = new NpgsqlCommand("TRUNCATE player_inventory;", conn))
        {
            await truncateInventory.ExecuteNonQueryAsync();
        }
    }

    private static int OccupiedBagSlotCount(PlayerInventorySnapshot snapshot)
    {
        var count = 0;
        foreach (var slot in snapshot.BagSlots)
        {
            if (!slot.IsEmpty)
            {
                count++;
            }
        }

        return count;
    }
}
