using GmRelay.Shared.Domain;
using GmRelay.Shared.Features.Sessions.CreateSession;
using GmRelay.Shared.Platform;
using Npgsql;
using Testcontainers.PostgreSql;

namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession;

[CollectionDefinition(Name)]
public sealed class CreateSessionHandlerPostgresCollection : ICollectionFixture<CreateSessionHandlerPostgresFixture>
{
    public const string Name = "Create session handler PostgreSQL";
}

public sealed class CreateSessionHandlerPostgresFixture : IAsyncLifetime
{
    private static readonly TimeSpan ContainerTimeout = TimeSpan.FromMinutes(2);
    private readonly PostgreSqlContainer container = new PostgreSqlBuilder("postgres:17-alpine").Build();

    public Task InitializeAsync()
    {
        return container.StartAsync().WaitAsync(ContainerTimeout);
    }

    public Task DisposeAsync()
    {
        return container.DisposeAsync().AsTask().WaitAsync(ContainerTimeout);
    }

    public async Task<string> CreateMigratedDatabaseAsync()
    {
        var databaseName = $"create_session_{Guid.NewGuid():N}";

        await using (var adminConnection = new NpgsqlConnection(container.GetConnectionString()))
        {
            await adminConnection.OpenAsync().WaitAsync(ContainerTimeout);
            await using var createDatabase = new NpgsqlCommand($"CREATE DATABASE \"{databaseName}\"", adminConnection);
            await createDatabase.ExecuteNonQueryAsync().WaitAsync(ContainerTimeout);
        }

        var connectionString = new NpgsqlConnectionStringBuilder(container.GetConnectionString())
        {
            Database = databaseName,
            Timeout = 10,
            CommandTimeout = 30
        }.ConnectionString;

        await using var connection = new NpgsqlConnection(connectionString);
        await connection.OpenAsync().WaitAsync(ContainerTimeout);

        foreach (var migration in GetMigrationPaths())
        {
            await using var command = new NpgsqlCommand(await File.ReadAllTextAsync(migration), connection)
            {
                CommandTimeout = 30
            };
            await command.ExecuteNonQueryAsync().WaitAsync(ContainerTimeout);
        }

        return connectionString;
    }

    private static IReadOnlyList<string> GetMigrationPaths()
    {
        var directory = new DirectoryInfo(AppContext.BaseDirectory);
        while (directory is not null)
        {
            var migrationsDirectory = Path.Combine(directory.FullName, "src", "GmRelay.Bot", "Migrations");
            if (Directory.Exists(migrationsDirectory))
            {
                return Directory.GetFiles(migrationsDirectory, "V*.sql")
                    .OrderBy(path => Path.GetFileName(path), StringComparer.Ordinal)
                    .ToArray();
            }

            directory = directory.Parent;
        }

        throw new DirectoryNotFoundException("Could not locate the bot migrations directory.");
    }
}

[Collection(CreateSessionHandlerPostgresCollection.Name)]
public sealed class CreateSessionHandlerIntegrationTests(CreateSessionHandlerPostgresFixture fixture)
{
    [Fact]
    public async Task HandleAsync_NewPlatformGroup_AddsOwnerAndPersistsSession()
    {
        var connectionString = await fixture.CreateMigratedDatabaseAsync();
        await using var dataSource = NpgsqlDataSource.Create(connectionString);
        var sut = new CreateSessionHandler(dataSource);

        var result = await sut.HandleAsync(
            new CreateSessionCommand(
                new PlatformUser(PlatformKind.Telegram, "111111111", "Test GM", "test_gm"),
                new PlatformGroup(PlatformKind.Telegram, "222222222", "Test Group"),
                "Test Adventure",
                string.Empty,
                [DateTimeOffset.UtcNow.AddDays(1)],
                null,
                null,
                GameSystem.Dnd5e,
                "Integration regression test",
                "Online",
                240,
                true),
            CancellationToken.None);

        Assert.True(result.Success, result.ErrorMessage);
        Assert.NotNull(result.BatchId);
        Assert.NotNull(result.GroupId);

        await using var connection = await dataSource.OpenConnectionAsync();
        await using var command = new NpgsqlCommand(
            """
            SELECT count(*)
            FROM group_managers gm
            JOIN players p ON p.id = gm.player_id
            WHERE gm.group_id = @group_id
              AND gm.role = 'Owner'
              AND p.platform = 'Telegram'
              AND p.external_user_id = '111111111'
            """,
            connection);
        command.Parameters.AddWithValue("group_id", result.GroupId.Value);

        var ownerCount = (long)(await command.ExecuteScalarAsync() ?? 0L);
        Assert.Equal(1, ownerCount);
    }
}
