using HealthChecks.UI.Client;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using ScadaLink.CentralUI;
using ScadaLink.ClusterInfrastructure;
using ScadaLink.Communication;
using ScadaLink.ConfigurationDatabase;
using ScadaLink.DeploymentManager;
using ScadaLink.ExternalSystemGateway;
using ScadaLink.HealthMonitoring;
using ScadaLink.Host;
using ScadaLink.Host.Actors;
using ScadaLink.Host.Health;
using ScadaLink.InboundAPI;
using ScadaLink.ManagementService;
using ScadaLink.NotificationService;
using ScadaLink.Security;
using ScadaLink.TemplateEngine;
using Serilog;

// SCADALINK_CONFIG determines which role-specific config to load (Central or Site)
// DOTNET_ENVIRONMENT/ASPNETCORE_ENVIRONMENT stay as "Development" for dev tooling (static assets, EF migrations, etc.)
var scadalinkConfig = Environment.GetEnvironmentVariable("SCADALINK_CONFIG")
    ?? Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
    ?? "Production";

var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false)
    .AddJsonFile($"appsettings.{scadalinkConfig}.json", optional: true)
    .AddEnvironmentVariables()
    .AddCommandLine(args)
    .Build();

// WP-11: Full startup validation — fail fast before any DI or actor system setup
StartupValidator.Validate(configuration);

// Read node options for Serilog enrichment
var nodeRole = configuration["ScadaLink:Node:Role"]!;
var nodeHostname = configuration["ScadaLink:Node:NodeHostname"] ?? "unknown";
var siteId = configuration["ScadaLink:Node:SiteId"] ?? "central";

// WP-14: Serilog structured logging
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(configuration)
    .Enrich.WithProperty("SiteId", siteId)
    .Enrich.WithProperty("NodeHostname", nodeHostname)
    .Enrich.WithProperty("NodeRole", nodeRole)
    .WriteTo.Console(outputTemplate:
        "[{Timestamp:HH:mm:ss} {Level:u3}] [{NodeRole}/{NodeHostname}] {Message:lj}{NewLine}{Exception}")
    .WriteTo.File("logs/scadalink-.log", rollingInterval: Serilog.RollingInterval.Day)
    .CreateLogger();

try
{
    Log.Information("Starting ScadaLink host as {Role} on {Hostname}", nodeRole, nodeHostname);

    if (nodeRole.Equals("Central", StringComparison.OrdinalIgnoreCase))
    {
        var builder = WebApplication.CreateBuilder(args);
        builder.Configuration.AddConfiguration(configuration);

        // WP-14: Serilog
        builder.Host.UseSerilog();

        // WP-17: Windows Service support (no-op when not running as a Windows Service)
        builder.Host.UseWindowsService();

        // Shared components
        builder.Services.AddClusterInfrastructure();
        builder.Services.AddCommunication();
        builder.Services.AddHealthMonitoring();
        builder.Services.AddCentralHealthAggregation();
        builder.Services.AddExternalSystemGateway();
        builder.Services.AddNotificationService();

        // Central-only components
        builder.Services.AddTemplateEngine();
        builder.Services.AddDeploymentManager();
        builder.Services.AddSecurity();
        builder.Services.AddCentralUI();
        builder.Services.AddInboundAPI();
        builder.Services.AddManagementService();

        var configDbConnectionString = configuration["ScadaLink:Database:ConfigurationDb"]
            ?? throw new InvalidOperationException("ScadaLink:Database:ConfigurationDb connection string is required for Central role.");
        builder.Services.AddConfigurationDatabase(configDbConnectionString);

        // WP-12: Health checks for readiness gating
        builder.Services.AddHealthChecks()
            .AddCheck<DatabaseHealthCheck>("database")
            .AddCheck<AkkaClusterHealthCheck>("akka-cluster")
            .AddCheck<ActiveNodeHealthCheck>("active-node");

        // WP-13: Akka.NET bootstrap via hosted service
        builder.Services.AddSingleton<AkkaHostedService>();
        builder.Services.AddHostedService(sp => sp.GetRequiredService<AkkaHostedService>());

        // Cluster node status provider scoped to the Central role — feeds the
        // CentralHealthReportLoop so the central cluster appears on /monitoring/health.
        builder.Services.AddSingleton<IClusterNodeProvider>(sp =>
        {
            var akkaService = sp.GetRequiredService<AkkaHostedService>();
            return new AkkaClusterNodeProvider(akkaService, "Central");
        });

        // Options binding
        SiteServiceRegistration.BindSharedOptions(builder.Services, builder.Configuration);
        builder.Services.Configure<SecurityOptions>(builder.Configuration.GetSection("ScadaLink:Security"));
        builder.Services.Configure<InboundApiOptions>(builder.Configuration.GetSection("ScadaLink:InboundApi"));
        builder.Services.Configure<DeploymentManagerOptions>(
            builder.Configuration.GetSection(ScadaLink.DeploymentManager.ServiceCollectionExtensions.OptionsSection));

        var app = builder.Build();

        // Apply or validate database migrations (skip when running in test harness)
        if (!string.Equals(configuration["ScadaLink:Database:SkipMigrations"], "true", StringComparison.OrdinalIgnoreCase))
        {
            var isDevelopment = app.Environment.IsDevelopment()
                || string.Equals(Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"), "Development", StringComparison.OrdinalIgnoreCase);
            using (var scope = app.Services.CreateScope())
            {
                var dbContext = scope.ServiceProvider.GetRequiredService<ScadaLinkDbContext>();
                var migrationLogger = scope.ServiceProvider
                    .GetRequiredService<ILoggerFactory>()
                    .CreateLogger(typeof(MigrationHelper).FullName!);
                await MigrationHelper.ApplyOrValidateMigrationsAsync(dbContext, isDevelopment, migrationLogger);
            }
        }

        // Middleware pipeline
        app.UseWebSockets();
        app.UseRouting();
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseAntiforgery();

        // WP-12: Map readiness endpoint — returns 503 until ready, 200 when ready.
        // REQ-HOST-4a defines readiness as cluster membership + DB connectivity,
        // explicitly NOT cluster leadership. The leader-only "active-node" check is
        // excluded here so a fully operational standby central node reports ready;
        // leadership is reported separately on /health/active.
        app.MapHealthChecks("/health/ready", new HealthCheckOptions
        {
            Predicate = check => check.Name != "active-node",
            ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
        });

        // Active node endpoint — returns 200 only on the cluster leader; used by Traefik for routing
        app.MapHealthChecks("/health/active", new HealthCheckOptions
        {
            Predicate = check => check.Name == "active-node",
            ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
        });

        app.MapStaticAssets();
        app.MapCentralUI<ScadaLink.Host.Components.App>();
        app.MapInboundAPI();
        app.MapManagementAPI();
        app.MapHub<ScadaLink.ManagementService.DebugStreamHub>("/hubs/debug-stream");

        // Compile and register all Inbound API method scripts at startup
        using (var scope = app.Services.CreateScope())
        {
            var apiRepo = scope.ServiceProvider.GetRequiredService<ScadaLink.Commons.Interfaces.Repositories.IInboundApiRepository>();
            var executor = app.Services.GetRequiredService<ScadaLink.InboundAPI.InboundScriptExecutor>();
            var methods = await apiRepo.GetAllApiMethodsAsync();
            foreach (var method in methods)
            {
                executor.CompileAndRegister(method);
            }
        }

        await app.RunAsync();
    }
    else if (nodeRole.Equals("Site", StringComparison.OrdinalIgnoreCase))
    {
        var builder = WebApplication.CreateBuilder(args);
        builder.Configuration.AddConfiguration(configuration);

        // WP-14: Serilog
        builder.Host.UseSerilog();

        // WP-17: Windows Service support (no-op when not running as a Windows Service)
        builder.Host.UseWindowsService();

        // Read GrpcPort from config (NodeOptions already has default 8083)
        var grpcPort = configuration.GetValue<int>("ScadaLink:Node:GrpcPort", 8083);

        // Configure Kestrel for HTTP/2 only on the gRPC port
        builder.WebHost.ConfigureKestrel(options =>
        {
            options.ListenAnyIP(grpcPort, listenOptions =>
            {
                listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http2;
            });
        });

        // gRPC server registration
        builder.Services.AddGrpc();
        builder.Services.AddSingleton<ScadaLink.Communication.Grpc.SiteStreamGrpcServer>();

        // Existing site service registrations
        SiteServiceRegistration.Configure(builder.Services, builder.Configuration);

        var app = builder.Build();

        // Map gRPC service — resolves the singleton SiteStreamGrpcServer from DI
        app.MapGrpcService<ScadaLink.Communication.Grpc.SiteStreamGrpcServer>();

        await app.RunAsync();
    }
    else
    {
        throw new InvalidOperationException($"Unknown role: {nodeRole}. Must be 'Central' or 'Site'.");
    }
}
catch (Exception ex)
{
    Log.Fatal(ex, "ScadaLink host terminated unexpectedly");
    throw;
}
finally
{
    await Log.CloseAndFlushAsync();
}

/// <summary>
/// Exposes the auto-generated Program class for test infrastructure (e.g. WebApplicationFactory).
/// </summary>
public partial class Program { }
