//-----------------------------------------------------------------------
// <copyright file="Program.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------

using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using AliasServerDb;
using AliasServerDb.Configuration;
using AliasVault.Logging;
using AliasVault.SmtpService;
using AliasVault.SmtpService.Handlers;
using AliasVault.SmtpService.Workers;
using AliasVault.WorkerStatus.ServiceExtensions;
using Microsoft.EntityFrameworkCore;
using SmtpServer;
using SmtpServer.Storage;

var builder = Host.CreateApplicationBuilder(args);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true);
builder.Services.ConfigureLogging(builder.Configuration, Assembly.GetExecutingAssembly().GetName().Name!, "../../../logs");

// Create global config object, get values from environment variables.
Config config = new Config();
var emailDomains = Environment.GetEnvironmentVariable("PRIVATE_EMAIL_DOMAINS")
                   ?? throw new KeyNotFoundException("PRIVATE_EMAIL_DOMAINS environment variable is not set.");
config.AllowedToDomains = emailDomains.Split(',').ToList();

var tlsEnabled = Environment.GetEnvironmentVariable("SMTP_TLS_ENABLED")
                 ?? throw new KeyNotFoundException("SMTP_TLS_ENABLED environment variable is not set.");
config.SmtpTlsEnabled = tlsEnabled;
builder.Services.AddSingleton(config);

builder.Services.AddAliasVaultSqliteConfiguration();
builder.Services.AddTransient<IMessageStore, DatabaseMessageStore>();
builder.Services.AddSingleton(
    provider =>
    {
        var options = new SmtpServerOptionsBuilder()
            .ServerName("aliasvault");

        if (config.SmtpTlsEnabled == "true")
        {
            // With TLS and certificate support.
            options.Endpoint(serverBuilder =>
                    serverBuilder
                        .Port(25, false)
                        .AllowUnsecureAuthentication()
                        .Certificate(CreateCertificate())
                        .SupportedSslProtocols(System.Security.Authentication.SslProtocols.Tls12))
                .Endpoint(serverBuilder =>
                    serverBuilder
                        .Port(587, false)
                        .AllowUnsecureAuthentication()
                        .Certificate(CreateCertificate())
                        .SupportedSslProtocols(System.Security.Authentication.SslProtocols.Tls12));
        }
        else
        {
            // No TLS
            options.Endpoint(serverBuilder =>
                    serverBuilder
                        .Port(25, false))
                .Endpoint(serverBuilder =>
                    serverBuilder
                        .Port(587, false));
        }

        return new SmtpServer.SmtpServer(options.Build(), provider.GetRequiredService<IServiceProvider>());

        static X509Certificate2 CreateCertificate()
        {
            // Specify the directory where PEM files are stored.
            string certificatesDirectory = "Certificates";

            // Get all PEM files in the directory.
            string[] pemFiles = Directory.GetFiles(certificatesDirectory, "*.pem");

            // Check if there are any PEM files found.
            if (pemFiles.Length == 0)
            {
                throw new FileNotFoundException("No PEM files found in the specified directory.");
            }

            // Use the first PEM file found
            string firstPemFile = pemFiles[0];

            // Create an X509Certificate2 object from the PEM file.
            var cert = X509Certificate2.CreateFromPemFile(firstPemFile);

            // Convert the X509Certificate2 object to a PFX file then immediately load it again
            // NOTE: this is important because saving the object to a PFX file to disk for a brief
            // second will allow Windows to correctly load the certificate with the private key.
            // If we don't do this, the certificate will be loaded without the private key and
            // will throw error on Windows:
            // "The TLS server credential's certificate does not have a private key information property attached to it"
            cert = new X509Certificate2(cert.Export(X509ContentType.Pfx));

            return cert;
        }
    });

// -----------------------------------------------------------------------
// Register hosted services via Status library wrapper in order to monitor and control (start/stop) them via the database.
// -----------------------------------------------------------------------
builder.Services.AddStatusHostedService<SmtpServerWorker, AliasServerDbContext>(Assembly.GetExecutingAssembly().GetName().Name!);

var host = builder.Build();

using (var scope = host.Services.CreateScope())
{
    var container = scope.ServiceProvider;
    var factory = container.GetRequiredService<IDbContextFactory<AliasServerDbContext>>();
    await using var context = await factory.CreateDbContextAsync();
    await context.Database.MigrateAsync();
}

await host.RunAsync();
