using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ScadaLink.ConfigurationDatabase;
using ScadaLink.Host.Actors;

namespace ScadaLink.IntegrationTests;

/// <summary>
/// Shared WebApplicationFactory for integration tests.
/// Replaces SQL Server with an in-memory database and skips migrations.
/// Removes AkkaHostedService to avoid DNS resolution issues in test environments.
/// Uses environment variables for config since Program.cs reads them in the initial ConfigurationBuilder
/// before WebApplicationFactory can inject settings.
/// </summary>
public class ScadaLinkWebApplicationFactory : WebApplicationFactory<Program>
{
    /// <summary>
    /// Environment variables that were set by this factory, to be cleaned up on dispose.
    /// </summary>
    private readonly Dictionary<string, string?> _previousEnvVars = new();

    public ScadaLinkWebApplicationFactory()
    {
        // The initial ConfigurationBuilder in Program.cs reads env vars with AddEnvironmentVariables().
        // The env var format uses __ as section separator.
        var envVars = new Dictionary<string, string>
        {
            ["DOTNET_ENVIRONMENT"] = "Development",
            ["ScadaLink__Node__Role"] = "Central",
            ["ScadaLink__Node__NodeHostname"] = "localhost",
            ["ScadaLink__Node__RemotingPort"] = "8081",
            ["ScadaLink__Cluster__SeedNodes__0"] = "akka.tcp://scadalink@localhost:8081",
            ["ScadaLink__Cluster__SeedNodes__1"] = "akka.tcp://scadalink@localhost:8082",
            ["ScadaLink__Database__ConfigurationDb"] = "Server=localhost;Database=ScadaLink_Test;TrustServerCertificate=True",
            ["ScadaLink__Database__MachineDataDb"] = "Server=localhost;Database=ScadaLink_MachineData_Test;TrustServerCertificate=True",
            ["ScadaLink__Database__SkipMigrations"] = "true",
            ["ScadaLink__Security__JwtSigningKey"] = "integration-test-signing-key-must-be-at-least-32-chars-long",
            ["ScadaLink__Security__LdapServer"] = "localhost",
            ["ScadaLink__Security__LdapPort"] = "3893",
            ["ScadaLink__Security__LdapUseTls"] = "false",
            ["ScadaLink__Security__AllowInsecureLdap"] = "true",
            ["ScadaLink__Security__LdapSearchBase"] = "dc=scadalink,dc=local",
        };

        foreach (var (key, value) in envVars)
        {
            _previousEnvVars[key] = Environment.GetEnvironmentVariable(key);
            Environment.SetEnvironmentVariable(key, value);
        }
    }

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

        builder.ConfigureServices(services =>
        {
            // Remove ALL DbContext and EF-related service registrations to avoid dual-provider conflict.
            // AddDbContext<> with UseSqlServer registers many internal services. We must remove them all.
            var descriptorsToRemove = services
                .Where(d =>
                    d.ServiceType == typeof(DbContextOptions<ScadaLinkDbContext>) ||
                    d.ServiceType == typeof(DbContextOptions) ||
                    d.ServiceType == typeof(ScadaLinkDbContext) ||
                    d.ServiceType.FullName?.Contains("EntityFrameworkCore") == true)
                .ToList();
            foreach (var d in descriptorsToRemove)
                services.Remove(d);

            // Add in-memory database as sole provider
            services.AddDbContext<ScadaLinkDbContext>(options =>
                options.UseInMemoryDatabase($"ScadaLink_IntegrationTests_{Guid.NewGuid()}"));

            // Remove AkkaHostedService to avoid Akka.NET remoting DNS resolution in tests.
            // It registers as both a singleton and a hosted service via factory.
            var akkaDescriptors = services
                .Where(d =>
                    d.ServiceType == typeof(AkkaHostedService) ||
                    (d.ServiceType == typeof(IHostedService) && d.ImplementationFactory != null))
                .ToList();
            foreach (var d in akkaDescriptors)
                services.Remove(d);
        });
    }

    protected override void Dispose(bool disposing)
    {
        base.Dispose(disposing);
        if (disposing)
        {
            foreach (var (key, previousValue) in _previousEnvVars)
            {
                Environment.SetEnvironmentVariable(key, previousValue);
            }
        }
    }
}
