using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.LocalDb;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.SiteEventLogging;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Deployment;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Persistence;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Repositories;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
using ZB.MOM.WW.ScadaBridge.SiteRuntime.Streaming;

namespace ZB.MOM.WW.ScadaBridge.SiteRuntime;

public static class ServiceCollectionExtensions
{
    /// <summary>
    /// Registers Site Runtime services including SiteStorageService, which persists to the
    /// consolidated site database resolved from <c>ILocalDb</c> (<c>LocalDb:Path</c>).
    /// </summary>
    /// <param name="services">The DI service collection to register services into.</param>
    /// <returns>The same <see cref="IServiceCollection"/> to allow chaining.</returns>
    public static IServiceCollection AddSiteRuntime(this IServiceCollection services)
    {
        // SiteStorageService takes ILocalDb (the consolidated site database at
        // LocalDb:Path) rather than a connection string, so there is nothing left for a
        // caller to supply — the string overload is gone and this is the only entry point.
        services.AddSingleton(sp => new SiteStorageService(
            sp.GetRequiredService<ILocalDb>(),
            sp.GetRequiredService<ILogger<SiteStorageService>>()));

        services.AddHostedService<SiteStorageInitializer>();

        // Script compilation service
        services.AddSingleton<ScriptCompilationService>();

        // Shared script library
        services.AddSingleton<SharedScriptLibrary>();

        // Site stream manager — registered as singleton and exposed as ISiteStreamSubscriber
        // so the gRPC server can subscribe relay actors to instance events.
        // ActorSystem is injected later via Initialize() after AkkaHostedService starts.
        services.AddSingleton(sp =>
        {
            var options = sp.GetRequiredService<IOptions<SiteRuntimeOptions>>().Value;
            var logger = sp.GetRequiredService<ILogger<SiteStreamManager>>();
            return new SiteStreamManager(options, logger);
        });
        services.AddSingleton<ISiteStreamSubscriber>(sp => sp.GetRequiredService<SiteStreamManager>());

        // Site-local cached-operation tracking store — the SQLite source of truth
        // that Tracking.Status(TrackedOperationId) reads and the cached-call telemetry
        // forwarder writes. Registered HERE (site-only) so the two comments that claim
        // "the operational tracking store is registered by AddSiteRuntime" are true
        // again (AuditLog SCE + AkkaHostedService). Before this (arch-review 08 round 2
        // NF4/T8) it had no DI registration anywhere in src/, so every consumer resolved
        // it as null and silently ran degraded (no cached-drain, no PullSiteCalls
        // reconciliation, Tracking.Status audit-only). Central roots never call
        // AddSiteRuntime, so this stays absent on central — matching the SITE-ONLY
        // contract. OperationTrackingOptions is bound + validated by the Host's
        // SiteServiceRegistration site-options block — but it no longer supplies the
        // store's database: the store takes ILocalDb (the consolidated site database
        // at LocalDb:Path), so OperationTrackingOptions.ConnectionString is now only
        // read by the site config DB. Resolving this store therefore forces ILocalDb
        // construction, which is what runs SiteLocalDbSetup.OnReady.
        services.AddSingleton<Commons.Interfaces.IOperationTrackingStore, Tracking.OperationTrackingStore>();

        // Site-local repository implementations backed by SQLite.
        // WP2.6a: ExternalSystemDefinitionCache is a singleton shared by every scoped
        // SiteExternalSystemRepository instance (one per script-execution DI scope), so
        // resolving the repository repeatedly does not each pay a fresh SQLite
        // fetch-all — DeploymentManagerActor invalidates the shared cache after an
        // artifact deploy applies external-system changes.
        services.AddSingleton<ExternalSystemDefinitionCache>();
        services.AddScoped<IExternalSystemRepository>(sp => new SiteExternalSystemRepository(
            sp.GetRequiredService<SiteStorageService>(),
            sp.GetRequiredService<ExternalSystemDefinitionCache>()));

        // Notify-and-fetch: typed HttpClient for fetching deployment configs from central.
        services.AddHttpClient<IDeploymentConfigFetcher, HttpDeploymentConfigFetcher>()
            .ConfigureHttpClient((sp, c) =>
                c.Timeout = TimeSpan.FromSeconds(
                    sp.GetRequiredService<IOptions<SiteRuntimeOptions>>().Value.ConfigFetchTimeoutSeconds));

        // S2/UA5: periodically lift the shared script-execution scheduler gauges
        // (queue depth, busy threads, oldest-busy age) onto the site health report.
        // Registered via factory-lambda so ISiteHealthCollector (registered by the
        // Host's AddSiteHealthMonitoring) is resolved lazily at host start.
        services.AddHostedService(sp => new ScriptSchedulerStatsReporter(
            sp.GetRequiredService<HealthMonitoring.ISiteHealthCollector>(),
            sp.GetRequiredService<IOptions<SiteRuntimeOptions>>().Value,
            sp.GetRequiredService<ILogger<ScriptSchedulerStatsReporter>>()));

        // WP2.6d: periodically lift SiteStreamManager's alarm-publish-queue drop count
        // onto the site health report.
        services.AddHostedService(sp => new Streaming.SiteStreamAlarmDropReporter(
            sp.GetRequiredService<HealthMonitoring.ISiteHealthCollector>(),
            sp.GetRequiredService<SiteStreamManager>(),
            sp.GetRequiredService<ILogger<Streaming.SiteStreamAlarmDropReporter>>()));

        // WP3.2 (site_events volume policy): the recorder is a singleton so ScriptRunLauncher
        // can resolve it from the root service provider on every run, the same way it already
        // resolves ISiteEventLogger. The flush service is registered ONLY here — central
        // composition roots never call AddSiteRuntime(), which is what makes the periodic
        // "script" summary event site-only, matching the design memo.
        services.AddSingleton<ScriptRunSummaryRecorder>();
        services.AddHostedService(sp => new ScriptRunSummaryFlushService(
            sp.GetRequiredService<ScriptRunSummaryRecorder>(),
            sp.GetRequiredService<ISiteEventLogger>(),
            sp.GetRequiredService<IOptionsMonitor<SiteRuntimeOptions>>(),
            sp.GetRequiredService<ILogger<ScriptRunSummaryFlushService>>()));

        return services;
    }

    /// <summary>Registers any additional DI services needed by the Site Runtime Akka actors.</summary>
    /// <param name="services">The DI service collection to register services into.</param>
    /// <returns>The same <see cref="IServiceCollection"/> to allow chaining.</returns>
    public static IServiceCollection AddSiteRuntimeActors(this IServiceCollection services)
    {
        // Actor registration is handled by AkkaHostedService.RegisterSiteActors()
        // which creates the DeploymentManager singleton and proxy
        return services;
    }
}
