using ErsatzTV.Infrastructure;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Sqlite.Data;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;

namespace ErsatzTV.Tests.Support;

/// <summary>
///     In-memory SQLite harness for handler/integration tests. A single <see cref="SqliteConnection" />
///     is kept open for the lifetime of the harness so the schema (built via
///     <see cref="DatabaseFacade.EnsureCreatedAsync" />) and data persist across the multiple
///     <see cref="TvContext" /> instances created by an <see cref="IDbContextFactory{TvContext}" />.
///     Foreign keys are disabled so partial graphs can be seeded without satisfying every FK.
/// </summary>
public sealed class InMemoryTvContext : IAsyncDisposable
{
    private readonly SqliteConnection _connection;
    private readonly DbContextOptions<TvContext> _options;

    private InMemoryTvContext(SqliteConnection connection, DbContextOptions<TvContext> options)
    {
        _connection = connection;
        _options = options;
    }

    public IDbContextFactory<TvContext> Factory => new TestDbContextFactory(_options);

    public static async Task<InMemoryTvContext> CreateAsync()
    {
        TvContext.IsSqlite = true;
        TvContext.IsUniqueConstraintViolation = SqliteErrorClassifier.IsUniqueConstraintViolation;

        var connection = new SqliteConnection("Data Source=:memory:;Foreign Keys=False");
        await connection.OpenAsync();

        DbContextOptions<TvContext> options = new DbContextOptionsBuilder<TvContext>()
            .UseSqlite(connection)
            .Options;

        await using (TvContext context = Create(options))
        {
            await context.Database.EnsureCreatedAsync();
            await context.Database.ExecuteSqlRawAsync("PRAGMA foreign_keys=OFF");
        }

        return new InMemoryTvContext(connection, options);
    }

    public TvContext CreateContext() => Create(_options);

    public async ValueTask DisposeAsync()
    {
        await _connection.DisposeAsync();
    }

    private static TvContext Create(DbContextOptions<TvContext> options) =>
        new(
            options,
            NullLoggerFactory.Instance,
            new SlowQueryInterceptor(NullLogger<SlowQueryInterceptor>.Instance));

    private sealed class TestDbContextFactory(DbContextOptions<TvContext> options) : IDbContextFactory<TvContext>
    {
        public TvContext CreateDbContext() => Create(options);
    }
}
