using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.MxGateway.Server;

namespace ZB.MOM.WW.MxGateway.Tests.Gateway;

// Sets process-global Kestrel/TLS environment variables that GatewayApplication.Build reads;
// serialized against all other collections so a parallel host-building test cannot inherit them
// mid-run and race on the generated-certificate file. See GlobalEnvironmentCollection.
[Collection(TestSupport.GlobalEnvironmentCollection.Name)]
public sealed class GatewayTlsBootstrapTests
{
    /// <summary>
    /// Verifies that when a Kestrel HTTPS endpoint is configured without its own certificate,
    /// the gateway supplies the generated self-signed certificate as the Kestrel HTTPS default.
    /// The host must start and bind, and the certificate served on the TLS handshake must be the
    /// gateway's generated cert (subject <c>CN=MxAccessGateway Self-Signed</c>) — not an ambient
    /// ASP.NET Core development certificate. On a host with no dev cert installed, starting a
    /// cert-less HTTPS endpoint throws "No server certificate was specified"; on a host that has a
    /// trusted dev cert, Kestrel would otherwise serve that dev cert (<c>CN=localhost</c>), so the
    /// subject assertion is what makes this test fail without the wiring on either kind of host.
    /// </summary>
    /// <returns>A task that represents the asynchronous operation.</returns>
    [Fact]
    public async Task Host_ServesGeneratedCertificate_WhenHttpsEndpointHasNoCertificate()
    {
        string certDir = Directory.CreateTempSubdirectory().FullName;
        try
        {
            Environment.SetEnvironmentVariable("Kestrel__Endpoints__Test__Url", "https://127.0.0.1:0");
            Environment.SetEnvironmentVariable(
                "MxGateway__Tls__SelfSignedCertPath", Path.Combine(certDir, "gw.pfx"));

            WebApplication app = GatewayApplication.Build([]);
            await app.StartAsync();
            try
            {
                string servedSubject = await ReadServedCertificateSubjectAsync(app);
                Assert.Contains("MxAccessGateway Self-Signed", servedSubject, StringComparison.Ordinal);
            }
            finally
            {
                await app.StopAsync();
                await app.DisposeAsync();
            }
        }
        finally
        {
            Environment.SetEnvironmentVariable("Kestrel__Endpoints__Test__Url", null);
            Environment.SetEnvironmentVariable("MxGateway__Tls__SelfSignedCertPath", null);
            Directory.Delete(certDir, recursive: true);
        }
    }

    private static async Task<string> ReadServedCertificateSubjectAsync(WebApplication app)
    {
        IServerAddressesFeature addresses =
            app.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>()
            ?? throw new InvalidOperationException("Server addresses feature was not available.");
        Uri endpoint = new(addresses.Addresses.First());

        using TcpClient client = new();
        await client.ConnectAsync(endpoint.Host, endpoint.Port);
        using SslStream ssl = new(
            client.GetStream(),
            leaveInnerStreamOpen: false,
            userCertificateValidationCallback: (_, _, _, _) => true);
        await ssl.AuthenticateAsClientAsync("127.0.0.1");

        return ssl.RemoteCertificate?.Subject ?? "(none)";
    }
}
