// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Security.Cryptography.X509Certificates;
using Aspire.Dashboard.Configuration;
using Aspire.Hosting;
using Grpc.Core;
using Grpc.Net.Client;
using Grpc.Net.Client.Configuration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.EnvironmentVariables;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Xunit.Abstractions;

namespace Aspire.Dashboard.Tests.Integration;

public static class IntegrationTestHelpers
{
    private static readonly X509Certificate2 s_testCertificate = TestCertificateLoader.GetTestCertificate();

    public static ILoggerFactory CreateLoggerFactory(ITestOutputHelper testOutputHelper, ITestSink? testSink = null)
    {
        return LoggerFactory.Create(builder =>
        {
            builder.AddXunit(testOutputHelper, LogLevel.Trace, DateTimeOffset.UtcNow);
            builder.SetMinimumLevel(LogLevel.Trace);
            if (testSink != null)
            {
                builder.AddProvider(new TestLoggerProvider(testSink));
            }
        });
    }

    public static DashboardWebApplication CreateDashboardWebApplication(
        ITestOutputHelper testOutputHelper,
        Action<Dictionary<string, string?>>? additionalConfiguration = null,
        ITestSink? testSink = null)
    {
        var loggerFactory = CreateLoggerFactory(testOutputHelper, testSink);

        return CreateDashboardWebApplication(loggerFactory, additionalConfiguration);
    }

    public static DashboardWebApplication CreateDashboardWebApplication(
        ILoggerFactory loggerFactory,
        Action<Dictionary<string, string?>>? additionalConfiguration = null)
    {
        var initialData = new Dictionary<string, string?>
        {
            [DashboardConfigNames.DashboardFrontendUrlName.ConfigKey] = "http://127.0.0.1:0",
            [DashboardConfigNames.DashboardOtlpGrpcUrlName.ConfigKey] = "http://127.0.0.1:0",
            [DashboardConfigNames.DashboardOtlpHttpUrlName.ConfigKey] = "http://127.0.0.1:0",
            [DashboardConfigNames.DashboardOtlpAuthModeName.ConfigKey] = nameof(OtlpAuthMode.Unsecured),
            [DashboardConfigNames.DashboardFrontendAuthModeName.ConfigKey] = nameof(FrontendAuthMode.Unsecured),
            // Allow the requirement of HTTPS communication with the OpenIdConnect authority to be relaxed during tests.
            ["Authentication:Schemes:OpenIdConnect:RequireHttpsMetadata"] = "false"
        };

        additionalConfiguration?.Invoke(initialData);

        var config = new ConfigurationManager()
            .AddInMemoryCollection(initialData).Build();

        var dashboardWebApplication = new DashboardWebApplication(builder =>
        {
            builder.Services.PostConfigure<LoggerFilterOptions>(o =>
            {
                o.Rules.Clear();
            });

            // Remove environment variable source of configuration.
            var sources = ((IConfigurationBuilder)builder.Configuration).Sources;
            foreach (var item in sources.ToList())
            {
                if (item is EnvironmentVariablesConfigurationSource)
                {
                    sources.Remove(item);
                }
            }
            builder.Configuration.AddConfiguration(config);

            builder.Services.AddSingleton(loggerFactory);
            builder.WebHost.ConfigureKestrel(serverOptions =>
            {
                serverOptions.ConfigureHttpsDefaults(options =>
                {
                    options.ServerCertificate = s_testCertificate;
                });
            });
        });

        return dashboardWebApplication;
    }

    public static HttpClient CreateHttpClient(
        string address,
        Action<X509Certificate2?>? validationCallback = null,
        X509CertificateCollection? clientCertificates = null)
    {
        var handler = new SocketsHttpHandler
        {
            SslOptions =
            {
                RemoteCertificateValidationCallback = (message, cert, chain, errors) =>
                {
                    validationCallback?.Invoke((X509Certificate2)cert!);
                    return true;
                }
            }
        };
        if (clientCertificates != null)
        {
            handler.SslOptions.ClientCertificates = clientCertificates;
        }

        return new HttpClient(handler) { BaseAddress = new Uri(address) };
    }

    public static GrpcChannel CreateGrpcChannel(
        string address,
        ITestOutputHelper testOutputHelper,
        Action<X509Certificate2?>? validationCallback = null,
        int? retryCount = null,
        X509CertificateCollection? clientCertificates = null)
    {
        var loggerFactory = LoggerFactory.Create(builder =>
        {
            builder.AddXunit(testOutputHelper);
            builder.SetMinimumLevel(LogLevel.Trace);
        });

        return CreateGrpcChannel(address, loggerFactory, validationCallback: validationCallback, retryCount: retryCount, clientCertificates: clientCertificates);
    }

    public static GrpcChannel CreateGrpcChannel(
        string address,
        ILoggerFactory loggerFactory,
        Action<X509Certificate2?>? validationCallback = null,
        int? retryCount = null,
        X509CertificateCollection? clientCertificates = null)
    {
        ServiceConfig? serviceConfig = null;
        if (retryCount > 0)
        {
            var defaultMethodConfig = new MethodConfig
            {
                Names = { MethodName.Default },
                RetryPolicy = new RetryPolicy
                {
                    MaxAttempts = retryCount,
                    InitialBackoff = TimeSpan.FromSeconds(1),
                    MaxBackoff = TimeSpan.FromSeconds(5),
                    BackoffMultiplier = 1.5,
                    RetryableStatusCodes = { StatusCode.Unavailable }
                }
            };

            serviceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } };
        }

        var handler = new SocketsHttpHandler
        {
            SslOptions =
            {
                RemoteCertificateValidationCallback = (message, cert, chain, errors) =>
                {
                    validationCallback?.Invoke((X509Certificate2)cert!);
                    return true;
                }
            }
        };
        if (clientCertificates != null)
        {
            handler.SslOptions.ClientCertificates = clientCertificates;
        }

        var channel = GrpcChannel.ForAddress(address, new()
        {
            HttpHandler = handler,
            LoggerFactory = loggerFactory,
            ServiceConfig = serviceConfig
        });
        return channel;
    }
}
