using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
using ZB.MOM.WW.OtOpcUa.Commons.Browsing;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browser;
using ZB.MOM.WW.OtOpcUa.Driver.Mqtt.Browser;
using ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser;
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Browser;
using ZB.MOM.WW.Secrets.Ui;

namespace ZB.MOM.WW.OtOpcUa.AdminUI;

public static class EndpointRouteBuilderExtensions
{
    /// <summary>
    /// Mounts the AdminUI Razor components and the AdminUI static asset pipeline at the root.
    /// Call from the fused Host's Program.cs alongside <c>app.MapOtOpcUaAuth()</c>.
    /// </summary>
    /// <typeparam name="TApp">The root component type for Razor pages.</typeparam>
    /// <param name="app">The endpoint route builder.</param>
    /// <returns>The same endpoint route builder, for chaining.</returns>
    public static IEndpointRouteBuilder MapAdminUI<TApp>(this IEndpointRouteBuilder app)
        where TApp : IComponent
    {
        // Razor class library static assets (_content/ZB.MOM.WW.OtOpcUa.AdminUI/**) are
        // served via the Host's app.UseStaticFiles() middleware which must run BEFORE
        // UseAuthentication() — see Program.cs.
        // The Secrets.Ui RCL's routable page is deliberately NOT registered here (lmxopcua#483):
        // this host's router is static with per-page @rendermode opt-in, so routing straight to
        // the RCL page rendered it without a circuit — it displayed but every @onclick was dead.
        // Pages/Secrets.razor in THIS assembly owns /admin/secrets instead (wrapping the RCL
        // page with the render mode); registering the RCL routes too would make the route
        // ambiguous.
        app.MapRazorComponents<TApp>()
            .AddInteractiveServerRenderMode();
        return app;
    }

    /// <summary>
    /// Adds AdminUI services to the dependency injection container.
    /// </summary>
    /// <param name="services">The service collection.</param>
    /// <returns>The same service collection, for chaining.</returns>
    public static IServiceCollection AddAdminUI(this IServiceCollection services)
    {
        services.AddRazorComponents().AddInteractiveServerComponents();
        services.AddOtOpcUaDriverStatusServices();

        // Secrets-management UI (ZB.MOM.WW.Secrets.Ui RCL, mounted at /admin/secrets). Register its
        // "secrets:manage"/"secrets:reveal" authorization policies additively onto the AuthorizationOptions
        // that AddOtOpcUaAuth (called just before AddAdminUI on admin nodes) sets up — Configure<T> stacks,
        // so this ADDS the two policies without disturbing FleetAdmin/DriverOperator/ConfigEditor. The
        // policies' AdminRole = "Administrator" reads the same ClaimTypes.Role claim as FleetAdmin, so an
        // existing Administrator satisfies them with no new group→role mapping. Registered here (the AdminUI
        // composition layer that already references the RCL) rather than in the core Security lib.
        services.Configure<AuthorizationOptions>(o => o.AddSecretsAuthorization());

        // Browse pipeline — see docs/plans/2026-05-28-driver-browsers-design.md
        services.AddSingleton<Browsing.BrowseSessionRegistry>();
        services.AddHostedService<Browsing.BrowseSessionReaper>();
        services.AddScoped<Browsing.IBrowserSessionService, Browsing.BrowserSessionService>();
        services.AddScoped<IUnsTreeService, UnsTreeService>();
        services.AddScoped<IRawTreeService, RawTreeService>();
        // v3 Batch 3 (WP2): authoring-time UNS effective-name uniqueness guard. Consumed by
        // UnsTreeService's reference/VirtualTag/ScriptedAlarm mutations (WP1); mirrors the
        // deploy-time DraftValidator UnsEffectiveNameCollision rule.
        services.AddScoped<IEffectiveNameGuard, EffectiveNameGuard>();
        // WP5 CSV export enumeration (read-only; does not extend the closed IRawTreeService prelude).
        services.AddScoped<RawTagCsvExportReader>();
        services.AddSingleton<IDriverBrowser, OpcUaClientDriverBrowser>();
        services.AddSingleton<IDriverBrowser, GalaxyDriverBrowser>();
        // Bespoke Sql schema browser (server → schema → table → column). Constructible bare — every
        // ctor param has a production default. The universal-browser DI line is deliberately NOT added
        // for Sql: SqlDriver.CanBrowse is false, and this bespoke IDriverBrowser wins by construction
        // (BrowserSessionService indexes bespoke browsers by DriverType before falling back to universal).
        services.AddSingleton<IDriverBrowser, SqlDriverBrowser>();
        services.AddSingleton<IDriverBrowser, MqttDriverBrowser>();
        // Universal Discover-backed fallback browser (Wave 0). IDriverFactory is bound by the
        // fused Host's DriverFactoryBootstrap; a standalone AdminUI has none → NullDriverFactory
        // → CanBrowse false → pickers gracefully stay manual-entry (design §6).
        services.AddSingleton<IUniversalDriverBrowser>(sp => new DiscoveryDriverBrowser(
            sp.GetService<IDriverFactory>() ?? NullDriverFactory.Instance,
            sp.GetRequiredService<ILogger<DiscoveryDriverBrowser>>()));

        // Roslyn-backed Monaco script-editor analysis (diagnostics/completions/hover/...).
        services.AddScoped<ScriptAnalysis.ScriptAnalysisService>();
        services.AddScoped<ScriptAnalysis.IScriptTagCatalog, ScriptAnalysis.ScriptTagCatalog>();

        // Certificate-store actions (trust/untrust/delete) for the /certificates page.
        services.AddSingleton<Certificates.CertificateStoreManager>();

        // Structured audit-event sink: forwards AuditEvents to the ControlPlane AuditWriter singleton.
        services.AddSingleton<ZB.MOM.WW.Audit.IAuditWriter, Audit.ActorAuditWriter>();

        return services;
    }
}
