using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.EntityFrameworkCore;
using OpenTelemetry.Metrics;
using Serilog;
using ZB.MOM.WW.OtOpcUa.Admin.Components;
using ZB.MOM.WW.OtOpcUa.Admin.Hubs;
using ZB.MOM.WW.OtOpcUa.Admin.Security;
using ZB.MOM.WW.OtOpcUa.Admin.Services;
using ZB.MOM.WW.OtOpcUa.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Host.UseSerilog((ctx, cfg) => cfg
    .MinimumLevel.Information()
    .WriteTo.Console()
    .WriteTo.File("logs/otopcua-admin-.log", rollingInterval: RollingInterval.Day));

builder.Services.AddRazorComponents().AddInteractiveServerComponents();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSignalR();

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(o =>
    {
        o.Cookie.Name = "OtOpcUa.Admin";
        o.LoginPath = "/login";
        o.ExpireTimeSpan = TimeSpan.FromHours(8);
    });

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("CanEdit", p => p.RequireRole(AdminRoles.ConfigEditor, AdminRoles.FleetAdmin))
    .AddPolicy("CanPublish", p => p.RequireRole(AdminRoles.FleetAdmin));

builder.Services.AddCascadingAuthenticationState();

builder.Services.AddDbContext<OtOpcUaConfigDbContext>(opt =>
    opt.UseSqlServer(builder.Configuration.GetConnectionString("ConfigDb")
        ?? throw new InvalidOperationException("ConnectionStrings:ConfigDb not configured")));

builder.Services.AddScoped<ClusterService>();
builder.Services.AddScoped<GenerationService>();
builder.Services.AddScoped<EquipmentService>();
builder.Services.AddScoped<UnsService>();
builder.Services.AddScoped<NamespaceService>();
builder.Services.AddScoped<DriverInstanceService>();
builder.Services.AddScoped<NodeAclService>();
builder.Services.AddScoped<PermissionProbeService>();
builder.Services.AddScoped<AclChangeNotifier>();
builder.Services.AddScoped<ReservationService>();
builder.Services.AddScoped<DraftValidationService>();
builder.Services.AddScoped<AuditLogService>();
builder.Services.AddScoped<HostStatusService>();
builder.Services.AddScoped<ClusterNodeService>();
builder.Services.AddSingleton<RedundancyMetrics>();
builder.Services.AddScoped<EquipmentImportBatchService>();
builder.Services.AddScoped<ZB.MOM.WW.OtOpcUa.Configuration.Services.ILdapGroupRoleMappingService,
    ZB.MOM.WW.OtOpcUa.Configuration.Services.LdapGroupRoleMappingService>();

// Phase 7 Stream F — scripting + virtual tag + scripted alarm draft services, test
// harness, and historian diagnostics. The historian sink is the Null variant here —
// the real SqliteStoreAndForwardSink lives in the server process. Admin reads status
// from whichever sink is provided at composition time.
builder.Services.AddScoped<ScriptService>();
builder.Services.AddScoped<VirtualTagService>();
builder.Services.AddScoped<ScriptedAlarmService>();
builder.Services.AddScoped<ScriptTestHarnessService>();
builder.Services.AddScoped<HistorianDiagnosticsService>();
builder.Services.AddSingleton<ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian.IAlarmHistorianSink>(
    ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian.NullAlarmHistorianSink.Instance);

// Cert-trust management — reads the OPC UA server's PKI store root so rejected client certs
// can be promoted to trusted via the Admin UI. Singleton: no per-request state, just
// filesystem operations.
builder.Services.Configure<CertTrustOptions>(builder.Configuration.GetSection(CertTrustOptions.SectionName));
builder.Services.AddSingleton<CertTrustService>();

// LDAP auth — parity with ScadaLink's LdapAuthService (decision #102).
builder.Services.Configure<LdapOptions>(
    builder.Configuration.GetSection("Authentication:Ldap"));
builder.Services.AddScoped<ILdapAuthService, LdapAuthService>();

// SignalR real-time fleet status + alerts (admin-ui.md §"Real-Time Updates").
builder.Services.AddHostedService<FleetStatusPoller>();

// OpenTelemetry Prometheus exporter — Meter stream from RedundancyMetrics + any future
// Admin-side instrumentation lands on the /metrics endpoint Prometheus scrapes. Pull-based
// means no OTel Collector deployment required for the common deploy-in-a-K8s case; appsettings
// Metrics:Prometheus:Enabled=false disables the endpoint entirely for locked-down deployments.
var metricsEnabled = builder.Configuration.GetValue("Metrics:Prometheus:Enabled", true);
if (metricsEnabled)
{
    builder.Services.AddOpenTelemetry()
        .WithMetrics(m => m
            .AddMeter(RedundancyMetrics.MeterName)
            .AddPrometheusExporter());
}

var app = builder.Build();

app.UseSerilogRequestLogging();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();

app.MapPost("/auth/logout", async (HttpContext ctx) =>
{
    await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    ctx.Response.Redirect("/");
});

app.MapHub<FleetStatusHub>("/hubs/fleet");
app.MapHub<AlertHub>("/hubs/alerts");

if (metricsEnabled)
{
    // Prometheus scrape endpoint — expose instrumentation registered in the OTel MeterProvider
    // above. Emits text-format metrics at /metrics; auth is intentionally NOT required (Prometheus
    // scrape jobs typically run on a trusted network). Operators who need auth put the endpoint
    // behind a reverse-proxy basic-auth gate per fleet-ops convention.
    app.MapPrometheusScrapingEndpoint();
}

app.MapRazorComponents<App>().AddInteractiveServerRenderMode();

await app.RunAsync();

public partial class Program;
