using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using StackExchange.Redis;

public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
{
    // Prefer CI/env overrides; fall back to local docker-compose.yml host ports.
    public static string PgConnection { get; } =
        Environment.GetEnvironmentVariable("ConnectionStrings__DefaultConnection")
        ?? "Host=localhost;Port=5436;Database=vigilcare_test;Username=postgres;Password=password";

    public static string RedisConnection { get; } =
        Environment.GetEnvironmentVariable("Redis__ConnectionString")
        ?? "localhost:6382,defaultDatabase=1,allowAdmin=true";

    public static string RabbitHost { get; } =
        Environment.GetEnvironmentVariable("RabbitMq__Host") ?? "localhost";

    public static int RabbitPort { get; } =
        int.TryParse(Environment.GetEnvironmentVariable("RabbitMq__Port"), out var p) ? p : 5674;

    public static string SimulationScenarioDirectory { get; } = Path.GetFullPath(Path.Combine(
        AppContext.BaseDirectory, "Fixtures", "Scenarios"));

    public TestSimulationClientFactory SimulationClientFactory =>
        Services.GetRequiredService<TestSimulationClientFactory>();

    // Override configuration to point at a test database — never run tests against
    // the development database; a botched rollback could corrupt seed data.
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.UseEnvironment("Testing");
        builder.ConfigureAppConfiguration((_, config) =>
        {
            config.AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["ConnectionStrings:DefaultConnection"] = PgConnection,
                ["Redis:ConnectionString"] = RedisConnection,
                ["RabbitMq:Host"] = RabbitHost,
                ["RabbitMq:Port"] = RabbitPort.ToString(),
                ["RabbitMq:Username"] = Environment.GetEnvironmentVariable("RabbitMq__Username") ?? "guest",
                ["RabbitMq:Password"] = Environment.GetEnvironmentVariable("RabbitMq__Password") ?? "guest",
                ["RabbitMq:PagingAckTimeoutMs"] = "5000",
                ["RabbitMq:VirtualHost"] = "vigilcare_test",
                // Neutralize appsettings.Production.json if the process env was Production.
                ["RabbitMq:UseSsl"] = "false",
                ["Kafka:BootstrapServers"] =
                    Environment.GetEnvironmentVariable("Kafka__BootstrapServers") ?? "localhost:9092",
                ["Kafka:ReplicationFactor"] =
                    Environment.GetEnvironmentVariable("Kafka__ReplicationFactor") ?? "1",
                ["Kafka:SecurityProtocol"] =
                    Environment.GetEnvironmentVariable("Kafka__SecurityProtocol") ?? "Plaintext",
                ["Kafka:NotificationPublisherGroupId"] = "notification-publisher-integration-test",
                ["Kafka:NotificationPublisherAutoOffsetReset"] = "Latest",
                ["Elasticsearch:Uri"] =
                    Environment.GetEnvironmentVariable("Elasticsearch__Uri") ?? "http://localhost:9200",
                ["Minio:Endpoint"] =
                    Environment.GetEnvironmentVariable("Minio__Endpoint") ?? "localhost:9005",
                ["Minio:UseSSL"] =
                    Environment.GetEnvironmentVariable("Minio__UseSSL") ?? "false",
                ["Fhir:ApiKey"] = "dev-integration-key-change-in-production",
                ["ApiKey:Gateway"] = GatewayAuthHelper.DevGatewayKey,
            });

            config.AddJsonFile("appsettings.Testing.json", optional: true, reloadOnChange: false);

            // Win over appsettings.Testing.json catalogue path / concurrency defaults.
            config.AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["Simulation:Enabled"] = "true",
                ["Simulation:ScenarioDirectory"] = SimulationScenarioDirectory,
                ["Simulation:MaxConcurrentRuns"] = "2",
                ["Simulation:MaxSpeed"] = "600",
                ["Simulation:RunHistoryLimit"] = "50",
            });
        });

        builder.ConfigureServices(services =>
        {
            services.Configure<HostOptions>(o =>
                o.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.Ignore);

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = TestingAuthHandler.SchemeName;
                options.DefaultChallengeScheme = TestingAuthHandler.SchemeName;
            })
            .AddScheme<AuthenticationSchemeOptions, TestingAuthHandler>(
                TestingAuthHandler.SchemeName, _ => { });
        });

        builder.ConfigureTestServices(services =>
        {
            services.RemoveAll<ISimulationClientFactory>();
            services.AddSingleton<TestSimulationClientFactory>(_ =>
                new TestSimulationClientFactory(this));
            services.AddSingleton<ISimulationClientFactory>(sp =>
                sp.GetRequiredService<TestSimulationClientFactory>());
        });
    }

    public async Task InitializeAsync()
    {
        // Apply migrations and clean stale data before the host starts — background
        // services such as ThresholdCacheLoader query the database during StartAsync,
        // so the reset must happen while no hosted service holds a lock.
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseNpgsql(PgConnection)
            .Options;
        await using (var migrateDb = new AppDbContext(options))
        {
            await migrateDb.Database.MigrateAsync();
            await DbResetHelper.ResetAsync(migrateDb);
        }

        await RabbitMqTestHelper.EnsureVirtualHostAsync(new RabbitMqOptions
        {
            Host = RabbitHost,
            Port = RabbitPort,
            Username = Environment.GetEnvironmentVariable("RabbitMq__Username") ?? "guest",
            Password = Environment.GetEnvironmentVariable("RabbitMq__Password") ?? "guest",
            VirtualHost = "vigilcare_test",
        });

        using var scope = Services.CreateScope();
        var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
        var server = redis.GetServer(redis.GetEndPoints().First());
        await server.FlushDatabaseAsync(1);
    }

    protected override void ConfigureClient(HttpClient client)
    {
        base.ConfigureClient(client);
        client.DefaultRequestHeaders.Add("X-Test-Role", "ADMIN");
    }

    public new async Task DisposeAsync()
    {
        await base.DisposeAsync();
    }
}
