using Books.Api.EventFlow.Infrastructure;
using EventFlow.PostgreSql;
using EventFlow.PostgreSql.Connections;
using EventFlow.PostgreSql.EventStores;
using Hangfire;
using Hangfire.InMemory;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Npgsql;

namespace Books.Api.Tests.Infrastructure;

/// <summary>
/// WebApplicationFactory configured for integration testing with:
/// - Isolated database per test class
/// - In-memory Hangfire (no real job processing)
/// - Full GraphQL endpoint support
/// </summary>
public class TestWebApplicationFactory : WebApplicationFactory<Program>
{
    static TestWebApplicationFactory()
    {
        // Enable legacy timestamp behavior for Npgsql 6.0+
        // Must be set before any Npgsql connections are created
        AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
    }

    private readonly TestDatabase _testDatabase = new();

    public string TestDatabaseName => _testDatabase.DatabaseName;
    public string ConnectionString => _testDatabase.ConnectionString;

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        // Set environment to Test to avoid Development-only middleware (like Altair)
        builder.UseEnvironment("Test");

        builder.ConfigureAppConfiguration((context, config) =>
        {
            // Clear all default configuration sources
            config.Sources.Clear();

            // Add test configuration with isolated database
            var testConfig = new Dictionary<string, string?>
            {
                ["ConnectionStrings:Default"] = _testDatabase.ConnectionString,
                ["AllowedHosts"] = "*"
            };

            config.AddInMemoryCollection(testConfig);

            Console.WriteLine($"[TestWebApplicationFactory] Using test database: {_testDatabase.DatabaseName}");
            Console.WriteLine($"[TestWebApplicationFactory] Connection string: {_testDatabase.ConnectionString}");
        });

        builder.ConfigureServices(services =>
        {
            // Run database migrations on the test database BEFORE services are built
            // This ensures the test database has the correct schema
            MigrateTestDatabase();

            // Replace the NpgsqlDataSource to use the test database
            services.RemoveAll<NpgsqlDataSource>();
            services.AddSingleton(new NpgsqlDataSourceBuilder(_testDatabase.ConnectionString).Build());

            // CRITICAL: Replace EventFlow's PostgreSQL configuration with test connection string
            // This ensures the event store uses the test database, not the original
            services.RemoveAll<IPostgreSqlConfiguration>();
            services.AddSingleton<IPostgreSqlConfiguration>(
                PostgreSqlConfiguration.New.SetConnectionString(_testDatabase.ConnectionString));

            // Replace only Hangfire storage (not EventFlow.Hangfire integration services)
            // We keep EventFlow.Hangfire services like IQueueNameProvider, HangfireJobScheduler
            var hangfireStorageDescriptors = services
                .Where(d =>
                    d.ServiceType.FullName?.Contains("Hangfire.JobStorage") == true ||
                    d.ServiceType.FullName?.Contains("Hangfire.Server.BackgroundJobServer") == true ||
                    d.ImplementationType?.FullName?.Contains("Hangfire.PostgreSql") == true)
                .ToList();

            foreach (var descriptor in hangfireStorageDescriptors)
            {
                services.Remove(descriptor);
            }

            // Add Hangfire with in-memory storage
            services.AddHangfire(config => config
                .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
                .UseSimpleAssemblyNameTypeSerializer()
                .UseRecommendedSerializerSettings()
                .UseInMemoryStorage());

            services.AddHangfireServer(options =>
            {
                options.WorkerCount = 1;
                options.Queues = ["default"];
            });
        });
    }

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

        // Run EventFlow PostgreSQL migrations after host is built
        using var scope = host.Services.CreateScope();
        var eventFlowMigrator = scope.ServiceProvider.GetRequiredService<IPostgreSqlDatabaseMigrator>();
        EventFlowEventStoresPostgreSql.MigrateDatabaseAsync(eventFlowMigrator, CancellationToken.None).Wait();

        return host;
    }

    private void MigrateTestDatabase()
    {
        // Run DbUp migrations for read model tables
        DatabaseMigrator.Migrate(_testDatabase.ConnectionString);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Reset Hangfire's static LogProvider before disposing to prevent
            // ObjectDisposedException when the next test class creates a new factory.
            Hangfire.Logging.LogProvider.SetCurrentLogProvider(null);

            _testDatabase.Dispose();
        }

        base.Dispose(disposing);
    }

    /// <summary>
    /// Creates an HttpClient configured for GraphQL requests.
    /// </summary>
    public HttpClient CreateGraphQLClient()
    {
        var client = CreateClient();
        client.BaseAddress = new Uri("http://localhost/graphql");
        return client;
    }
}
