using System.Collections.Generic;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Mongo2Go;

namespace StellaOps.Scanner.WebService.Tests;

internal sealed class ScannerApplicationFactory : WebApplicationFactory<Program>
{
    private readonly MongoDbRunner mongoRunner;
    private readonly Dictionary<string, string?> configuration = new()
    {
        ["scanner:storage:driver"] = "mongo",
        ["scanner:storage:dsn"] = string.Empty,
        ["scanner:queue:driver"] = "redis",
        ["scanner:queue:dsn"] = "redis://localhost:6379",
        ["scanner:artifactStore:driver"] = "minio",
        ["scanner:artifactStore:endpoint"] = "https://minio.local",
        ["scanner:artifactStore:accessKey"] = "test-access",
        ["scanner:artifactStore:secretKey"] = "test-secret",
        ["scanner:artifactStore:bucket"] = "scanner-artifacts",
        ["scanner:telemetry:minimumLogLevel"] = "Information",
        ["scanner:telemetry:enableRequestLogging"] = "false",
        ["scanner:features:enableSignedReports"] = "false"
    };

    private readonly Action<IDictionary<string, string?>>? configureConfiguration;
    private readonly Action<IServiceCollection>? configureServices;

    public ScannerApplicationFactory(
        Action<IDictionary<string, string?>>? configureConfiguration = null,
        Action<IServiceCollection>? configureServices = null)
    {
        EnsureMongo2GoEnvironment();
        mongoRunner = MongoDbRunner.Start(singleNodeReplSet: true);
        configuration["scanner:storage:dsn"] = mongoRunner.ConnectionString;
        this.configureConfiguration = configureConfiguration;
        this.configureServices = configureServices;
    }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        configureConfiguration?.Invoke(configuration);

        builder.UseEnvironment("Testing");

        Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ENABLED", null);
        Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ALLOWANONYMOUSFALLBACK", null);
        Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ISSUER", null);
        Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__AUDIENCES__0", null);
        Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTID", null);
        Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTSECRET", null);
        Environment.SetEnvironmentVariable("SCANNER__STORAGE__DSN", configuration["scanner:storage:dsn"]);
        Environment.SetEnvironmentVariable("SCANNER__QUEUE__DSN", configuration["scanner:queue:dsn"]);
        Environment.SetEnvironmentVariable("SCANNER__ARTIFACTSTORE__ENDPOINT", configuration["scanner:artifactStore:endpoint"]);
        Environment.SetEnvironmentVariable("SCANNER__ARTIFACTSTORE__ACCESSKEY", configuration["scanner:artifactStore:accessKey"]);
        Environment.SetEnvironmentVariable("SCANNER__ARTIFACTSTORE__SECRETKEY", configuration["scanner:artifactStore:secretKey"]);

        if (configuration.TryGetValue("scanner:authority:enabled", out var authorityEnabled))
        {
            Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ENABLED", authorityEnabled);
        }

        if (configuration.TryGetValue("scanner:authority:allowAnonymousFallback", out var allowAnonymous))
        {
            Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ALLOWANONYMOUSFALLBACK", allowAnonymous);
        }

        if (configuration.TryGetValue("scanner:authority:issuer", out var authorityIssuer))
        {
            Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__ISSUER", authorityIssuer);
        }

        if (configuration.TryGetValue("scanner:authority:audiences:0", out var primaryAudience))
        {
            Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__AUDIENCES__0", primaryAudience);
        }

        if (configuration.TryGetValue("scanner:authority:clientId", out var clientId))
        {
            Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTID", clientId);
        }

        if (configuration.TryGetValue("scanner:authority:clientSecret", out var clientSecret))
        {
            Environment.SetEnvironmentVariable("SCANNER__AUTHORITY__CLIENTSECRET", clientSecret);
        }

        builder.ConfigureAppConfiguration((_, configBuilder) =>
        {
            configBuilder.AddInMemoryCollection(configuration);
        });

        builder.ConfigureTestServices(services =>
        {
            configureServices?.Invoke(services);
        });
    }

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

        if (disposing)
        {
            mongoRunner.Dispose();
        }
    }

    private static void EnsureMongo2GoEnvironment()
    {
        if (!OperatingSystem.IsLinux())
        {
            return;
        }

        var libraryPath = ResolveOpenSslLibraryPath();
        if (libraryPath is null)
        {
            return;
        }

        var existing = Environment.GetEnvironmentVariable("LD_LIBRARY_PATH");
        if (string.IsNullOrEmpty(existing))
        {
            Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", libraryPath);
            return;
        }

        var segments = existing.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (Array.IndexOf(segments, libraryPath) < 0)
        {
            Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", string.Join(':', new[] { libraryPath }.Concat(segments)));
        }
    }

    private static string? ResolveOpenSslLibraryPath()
    {
        var current = AppContext.BaseDirectory;
        while (!string.IsNullOrEmpty(current))
        {
            var candidate = Path.Combine(current, "tools", "openssl", "linux-x64");
            if (Directory.Exists(candidate))
            {
                return candidate;
            }

            var parent = Directory.GetParent(current);
            if (parent is null)
            {
                break;
            }

            current = parent.FullName;
        }

        return null;
    }
}
