using Baya.Infrastructure.Identity.Identity.SeedDatabaseService;
using Baya.Infrastructure.Persistence;
using Baya.Infrastructure.Persistence.Interceptors;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;

namespace Baya.Test.Api;

/// <summary>
/// Boots the whole API (Program.cs wiring: envelope filters, JWE auth, rate limiter, Mediator) in the
/// "Testing" environment over an isolated in-memory SQLite database. Program skips SQL Server
/// migrations/seeding for this environment; the factory does EnsureCreated + the role/admin seed.
/// Use one factory per test class — the rate limiter is per-host, so a fresh host keeps each class
/// inside the OTP/auth per-IP budgets.
/// </summary>
public sealed class BayaApiFactory : WebApplicationFactory<Program>
{
    // A named shared-cache in-memory database (kept alive by this connection) instead of a single
    // shared SqliteConnection instance: request scopes and hosted services open their own
    // connections, so nothing initializes one connection concurrently.
    private readonly string _connectionString =
        $"Data Source={Guid.NewGuid():N};Mode=Memory;Cache=Shared";

    private readonly SqliteConnection _keepAlive;

    public BayaApiFactory()
    {
        _keepAlive = new SqliteConnection(_connectionString);
        _keepAlive.Open();

        // The committed appsettings.json ships placeholder JWE keys (real ones come from user-secrets /
        // env in Development / deploy). The Testing host has neither, so supply working test keys via
        // environment variables — they sit after appsettings.json in the default config chain, so they
        // reliably override the placeholders. The Encrypt key must be exactly 16 bytes for the AES-128
        // JWE; field-encryption keeps the mock seam's placeholder (it derives a key from any string).
        Environment.SetEnvironmentVariable("IdentitySettings__SecretKey", "testing-only-jwe-signing-key-0123456789abcdef");
        Environment.SetEnvironmentVariable("IdentitySettings__Encryptkey", "testing-16-bytes");
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Testing");

        builder.ConfigureServices(services =>
        {
            // Swap the SQL Server DbContext for in-memory SQLite. EF 8+ keeps AddDbContext's
            // option lambda in IDbContextOptionsConfiguration — it must go too, or both providers
            // end up configured on the same options.
            services.RemoveAll(typeof(IDbContextOptionsConfiguration<ApplicationDbContext>));
            services.RemoveAll(typeof(DbContextOptions<ApplicationDbContext>));

            services.AddDbContext<ApplicationDbContext>((serviceProvider, options) =>
            {
                options
                    .UseSqlite(_connectionString)
                    .AddInterceptors(serviceProvider.GetRequiredService<AuditFieldInterceptor>());
            });
        });
    }

    protected override IHost CreateHost(IHostBuilder builder)
    {
        var host = base.CreateHost(builder);

        using var scope = host.Services.CreateScope();
        scope.ServiceProvider.GetRequiredService<ApplicationDbContext>().Database.EnsureCreated();
        scope.ServiceProvider.GetRequiredService<ISeedDataBase>().Seed().GetAwaiter().GetResult();

        return host;
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        _keepAlive.Dispose();
    }
}