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

namespace NeonSprawl.Server.Tests.Game.Gathering;

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

    private const string AlphaId = "prototype_resource_node_alpha";

    [RequirePostgresFact]
    public async Task TryCommitSuccessfulGatherDecrementAcrossNewFactory_ShouldPersistRemainingGathers()
    {
        // Arrange
        await ResetInstanceTableAsync();
        ResourceNodeInstanceMutationOutcome firstOutcome;

        // Act — write through first host
        using (var firstScope = Factory.Services.CreateScope())
        {
            var registry = firstScope.ServiceProvider.GetRequiredService<IResourceNodeDefinitionRegistry>();
            var store = firstScope.ServiceProvider.GetRequiredService<IResourceNodeInstanceStore>();
            firstOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, 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<IResourceNodeInstanceStore>();
        var readBack = readStore.TryGetRemainingGathers(AlphaId, out var snapshot);

        // Assert
        Assert.Equal(ResourceNodeInstanceMutationKind.Applied, firstOutcome.Kind);
        Assert.Equal(9, firstOutcome.Snapshot!.Value.RemainingGathers);
        Assert.True(readBack);
        Assert.Equal(9, snapshot.RemainingGathers);
    }

    [RequirePostgresFact]
    public async Task TryCommitSuccessfulGatherDecrement_WhenDepleted_ShouldPersistZeroRow()
    {
        // Arrange
        await ResetInstanceTableAsync();
        using (var seedScope = Factory.Services.CreateScope())
        {
            var registry = seedScope.ServiceProvider.GetRequiredService<IResourceNodeDefinitionRegistry>();
            var store = seedScope.ServiceProvider.GetRequiredService<IResourceNodeInstanceStore>();
            for (var i = 0; i < 10; i++)
            {
                _ = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, registry, store);
            }
        }

        // Act
        ResourceNodeInstanceMutationOutcome denyOutcome;
        ResourceNodeInstanceSnapshot persisted;
        await using var secondFactory = new PostgresWebApplicationFactory();
        using (var scope = secondFactory.Services.CreateScope())
        {
            var registry = scope.ServiceProvider.GetRequiredService<IResourceNodeDefinitionRegistry>();
            var store = scope.ServiceProvider.GetRequiredService<IResourceNodeInstanceStore>();
            denyOutcome = ResourceNodeInstanceOperations.TryCommitSuccessfulGatherDecrement(AlphaId, registry, store);
            store.TryGetRemainingGathers(AlphaId, out persisted);
        }

        // Assert
        Assert.Equal(ResourceNodeInstanceMutationKind.Denied, denyOutcome.Kind);
        Assert.Equal(ResourceNodeInstanceReasonCodes.NodeDepleted, denyOutcome.ReasonCode);
        Assert.Equal(0, persisted.RemainingGathers);
    }

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

        _ = Factory.Services;
        var ddlPath = Path.Combine(AppContext.BaseDirectory, "db", "migrations", "V006__resource_node_instance.sql");
        if (!File.Exists(ddlPath))
        {
            throw new FileNotFoundException($"Test DDL not found at '{ddlPath}'.", ddlPath);
        }

        var ddl = await File.ReadAllTextAsync(ddlPath);
        await using var conn = new NpgsqlConnection(cs);
        await conn.OpenAsync();
        await using (var apply = new NpgsqlCommand(ddl, conn))
        {
            await apply.ExecuteNonQueryAsync();
        }

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