using Microsoft.EntityFrameworkCore;
using TeleWave.Infrastructure.Persistence;
using Testcontainers.PostgreSql;
using Xunit;

namespace TeleWave.Integration.Tests;

/// <summary>
/// Поднимает одноразовый Postgres в контейнере и применяет к нему реальные миграции. Даёт свежие
/// экземпляры <see cref="AppDbContext"/> (каждый — своё соединение), чтобы тестировать то, что InMemory
/// не умеет: транзакции, advisory-lock, ExecuteDelete, raw SQL. Требует запущенного Docker.
/// </summary>
public sealed class PostgresFixture : IAsyncLifetime
{
    // Контейнер создаётся внутри InitializeAsync: сам билдер бросает, когда Docker недоступен,
    // и в инициализаторе поля это ронял бы всю коллекцию тестов вместо честного пропуска.
    private PostgreSqlContainer? _container;

    /// <summary>false, если Docker недоступен (напр. CI-раннер без Docker) — тогда тесты пропускаются.</summary>
    public bool Available { get; private set; }

    public string ConnectionString =>
        _container?.GetConnectionString()
        ?? throw new InvalidOperationException("Контейнер не запущен — проверяйте Available.");

    public async Task InitializeAsync()
    {
        try
        {
            _container = new PostgreSqlBuilder().WithImage("postgres:16-alpine").Build();
            await _container.StartAsync();
            await using var db = CreateContext();
            await db.Database.MigrateAsync();
            Available = true;
        }
        catch (Exception)
        {
            // Docker не запущен/недоступен — интеграционные тесты будут пропущены (Skip), а не упадут.
            Available = false;
        }
    }

    public AppDbContext CreateContext() =>
        new(new DbContextOptionsBuilder<AppDbContext>().UseNpgsql(ConnectionString).Options);

    public async Task DisposeAsync()
    {
        if (_container is not null)
            await _container.DisposeAsync();
    }
}

[CollectionDefinition("postgres")]
public sealed class PostgresCollection : ICollectionFixture<PostgresFixture>;

/// <summary>Детерминированный источник случайности для планировщика в тестах.</summary>
internal sealed class SequenceRandom(params int[] sequence)
    : Domain.Broadcast.Scheduling.IRandomSource
{
    private readonly int[] _sequence = sequence.Length == 0 ? [0] : sequence;
    private int _i;

    public int Next(int maxExclusive)
    {
        if (maxExclusive <= 0)
            return 0;
        var value = _sequence[_i++ % _sequence.Length];
        return ((value % maxExclusive) + maxExclusive) % maxExclusive;
    }
}
