using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;
using ZB.MOM.WW.Health;
using ZB.MOM.WW.MxGateway.Contracts;
using ZB.MOM.WW.MxGateway.Server.Alarms;
using ZB.MOM.WW.MxGateway.Server.Configuration;
using ZB.MOM.WW.MxGateway.Server.Dashboard;
using ZB.MOM.WW.MxGateway.Server.Diagnostics;
using ZB.MOM.WW.MxGateway.Server.Galaxy;
using ZB.MOM.WW.MxGateway.Server.Grpc;
using ZB.MOM.WW.MxGateway.Server.Metrics;
using ZB.MOM.WW.MxGateway.Server.Security.Authentication;
using ZB.MOM.WW.MxGateway.Server.Security.Authorization;
using ZB.MOM.WW.MxGateway.Server.Sessions;
using ZB.MOM.WW.MxGateway.Server.Workers;
using ZB.MOM.WW.Telemetry;
using ZB.MOM.WW.Telemetry.Serilog;

namespace ZB.MOM.WW.MxGateway.Server;

/// <summary>
/// Configures and builds the gateway web application.
/// </summary>
public static class GatewayApplication
{
    private const string StaticAssetsManifestFileName = "ZB.MOM.WW.MxGateway.Server.staticwebassets.endpoints.json";

    /// <summary>
    /// Builds a configured web application with all gateway services and middleware.
    /// </summary>
    /// <param name="args">Command-line arguments passed to the application.</param>
    /// <returns>A configured web application ready to run.</returns>
    public static WebApplication Build(string[] args)
    {
        WebApplicationBuilder builder = CreateBuilder(args);
        WebApplication app = builder.Build();

        app.UseGatewayRequestLoggingScope();
        app.UseStaticFiles();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseAntiforgery();
        app.MapGatewayEndpoints();

        return app;
    }

    /// <summary>
    /// Creates a web application builder configured with gateway services.
    /// </summary>
    /// <param name="args">Command-line arguments passed to the application.</param>
    /// <returns>A configured web application builder.</returns>
    public static WebApplicationBuilder CreateBuilder(string[] args)
    {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(new WebApplicationOptions
        {
            Args = args,
            ContentRootPath = ResolveContentRootPath(),
        });
        StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration);

        ConfigureSelfSignedTls(builder);

        builder.AddZbSerilog(o => o.ServiceName = "mxgateway");

        builder.Services.AddGatewayConfiguration(builder.Configuration);
        builder.Services.AddSqliteAuthStore(builder.Configuration);
        builder.Services.AddGatewayGrpcAuthorization();
        builder.Services.AddHealthChecks()
            .AddTypeActivatedCheck<AuthStoreHealthCheck>(
                "auth-store",
                failureStatus: null,
                tags: new[] { ZbHealthTags.Ready });
        builder.Services.AddSingleton<GatewayMetrics>();
        builder.AddZbTelemetry(o =>
        {
            o.ServiceName = "mxgateway";
            o.Meters = [GatewayMetrics.MeterName]; // "MxGateway.Server" — name unchanged
            if (Enum.TryParse<ZbExporter>(builder.Configuration["MxGateway:Telemetry:Exporter"], ignoreCase: true, out var exporter))
                o.Exporter = exporter;
            var otlp = builder.Configuration["MxGateway:Telemetry:OtlpEndpoint"];
            if (!string.IsNullOrWhiteSpace(otlp))
                o.OtlpEndpoint = otlp;
        });
        builder.Services.AddSingleton<ILogRedactor, GatewayLogRedactorSeam>();
        builder.Services.AddSingleton<MxAccessGrpcMapper>();
        builder.Services.AddSingleton<MxAccessGrpcRequestValidator>();
        builder.Services.AddSingleton<IEventStreamService, EventStreamService>();
        builder.Services.AddWorkerProcessLauncher();
        builder.Services.AddGatewaySessions();
        builder.Services.AddGatewayAlarms();
        builder.Services.AddGatewayDashboard(builder.Configuration);
        builder.Services.AddGalaxyRepository();

        return builder;
    }

    private static void ConfigureSelfSignedTls(WebApplicationBuilder builder)
    {
        if (!Security.Tls.KestrelTlsInspector.RequiresGeneratedCertificate(builder.Configuration))
        {
            return;
        }

        Configuration.TlsOptions tlsOptions =
            builder.Configuration.GetSection("MxGateway:Tls").Get<Configuration.TlsOptions>()
            ?? new Configuration.TlsOptions();

        using ILoggerFactory loggerFactory = LoggerFactory.Create(logging =>
        {
            logging.AddConfiguration(builder.Configuration.GetSection("Logging"));
            logging.AddConsole();
        });
        Security.Tls.SelfSignedCertificateProvider provider = new(
            tlsOptions,
            loggerFactory.CreateLogger<Security.Tls.SelfSignedCertificateProvider>(),
            TimeProvider.System);

        X509Certificate2 certificate = provider.LoadOrCreate();
        builder.WebHost.ConfigureKestrel(options =>
            // The certificate is intentionally owned by Kestrel for the application
            // lifetime; it is not disposed here.
            options.ConfigureHttpsDefaults(https => https.ServerCertificate = certificate));
    }

    private static string ResolveContentRootPath()
    {
        string? configuredContentRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_CONTENTROOT");
        if (!string.IsNullOrWhiteSpace(configuredContentRootPath)
            && IsServerContentRoot(configuredContentRootPath))
        {
            return configuredContentRootPath;
        }

        string currentDirectory = Environment.CurrentDirectory;
        if (IsServerContentRoot(currentDirectory))
        {
            return currentDirectory;
        }

        string baseDirectory = AppContext.BaseDirectory;
        if (IsServerContentRoot(baseDirectory))
        {
            return baseDirectory;
        }

        string? discoveredContentRootPath = DiscoverServerContentRoot(currentDirectory)
            ?? DiscoverServerContentRoot(baseDirectory);

        return discoveredContentRootPath ?? baseDirectory;
    }

    private static string? DiscoverServerContentRoot(string startPath)
    {
        DirectoryInfo? directory = new(startPath);
        while (directory is not null)
        {
            if (IsServerContentRoot(directory.FullName))
            {
                return directory.FullName;
            }

            string serverProjectPath = Path.Combine(directory.FullName, "src", "ZB.MOM.WW.MxGateway.Server");
            if (IsServerContentRoot(serverProjectPath))
            {
                return serverProjectPath;
            }

            directory = directory.Parent;
        }

        return null;
    }

    private static bool IsServerContentRoot(string path)
    {
        return File.Exists(Path.Combine(path, "appsettings.json"))
            && Directory.Exists(Path.Combine(path, "wwwroot"));
    }

    /// <summary>
    /// Maps gateway endpoints including gRPC services, health checks, and the dashboard.
    /// </summary>
    /// <param name="endpoints">Endpoint route builder to map endpoints to.</param>
    /// <returns>The same endpoint route builder for chaining.</returns>
    public static IEndpointRouteBuilder MapGatewayEndpoints(this IEndpointRouteBuilder endpoints)
    {
        endpoints.MapStaticAssets(ResolveStaticAssetsManifestPath());

        endpoints.MapZbHealth();
        endpoints.MapZbMetrics();

        endpoints.MapGrpcService<MxAccessGatewayService>();
        endpoints.MapGrpcService<GalaxyRepositoryGrpcService>();
        endpoints.MapGatewayDashboard();

        return endpoints;
    }

    private static string ResolveStaticAssetsManifestPath()
    {
        string manifestPath = Path.Combine(AppContext.BaseDirectory, StaticAssetsManifestFileName);

        return File.Exists(manifestPath) ? manifestPath : StaticAssetsManifestFileName;
    }
}
