using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ScadaLink.AuditLog.Configuration;
using ScadaLink.AuditLog.Site;
using ScadaLink.AuditLog.Site.Telemetry;
using ScadaLink.Commons.Interfaces.Services;

namespace ScadaLink.AuditLog;

/// <summary>
/// Composition root for the Audit Log (#23) component.
/// </summary>
/// <remarks>
/// <para>
/// M1 registered <see cref="AuditLogOptions"/> + the validator. M2 Bundle E
/// extends the surface with the site-side writer chain
/// (<see cref="SqliteAuditWriter"/> + <see cref="RingBufferFallback"/> +
/// <see cref="FallbackAuditWriter"/>) and the telemetry collaborators
/// (<see cref="ISiteAuditQueue"/>, <see cref="ISiteStreamAuditClient"/>,
/// <see cref="IAuditWriteFailureCounter"/>, <see cref="SiteAuditTelemetryOptions"/>,
/// <see cref="SqliteAuditWriterOptions"/>).
/// </para>
/// <para>
/// Audit Log (#23) sits alongside Notification Outbox (#21) and Site Call
/// Audit (#22). <c>IAuditLogRepository</c> is registered by
/// <c>ScadaLink.ConfigurationDatabase.ServiceCollectionExtensions.AddConfigurationDatabase</c>,
/// so the caller (the Host on the central node) must also call that.
/// </para>
/// </remarks>
public static class ServiceCollectionExtensions
{
    /// <summary>Configuration section bound to <see cref="AuditLogOptions"/>.</summary>
    public const string ConfigSectionName = "AuditLog";

    /// <summary>Configuration section bound to <see cref="SqliteAuditWriterOptions"/>.</summary>
    public const string SiteWriterSectionName = "AuditLog:SiteWriter";

    /// <summary>Configuration section bound to <see cref="SiteAuditTelemetryOptions"/>.</summary>
    public const string SiteTelemetrySectionName = "AuditLog:SiteTelemetry";

    /// <summary>
    /// Registers the Audit Log (#23) component services: options, the site
    /// SQLite writer chain (primary + ring fallback + failure-counter sink),
    /// and the site-→central telemetry collaborators. Idempotent re-registration
    /// is not supported; call this exactly once per <see cref="IServiceCollection"/>.
    /// </summary>
    public static IServiceCollection AddAuditLog(this IServiceCollection services, IConfiguration config)
    {
        ArgumentNullException.ThrowIfNull(services);
        ArgumentNullException.ThrowIfNull(config);

        // M1: top-level AuditLogOptions + validator (redaction policy, payload caps, etc.).
        services.AddOptions<AuditLogOptions>()
            .Bind(config.GetSection(ConfigSectionName))
            .ValidateOnStart();
        services.AddSingleton<IValidateOptions<AuditLogOptions>, AuditLogOptionsValidator>();

        // M2 Bundle E: site writer + telemetry options bindings.
        // BindConfiguration is not used because the configuration root supplied
        // by the caller may not be the application root — we go through the
        // section explicitly so a partial IConfiguration (e.g. a test stub
        // anchored on the AuditLog section's parent) still works.
        services.AddOptions<SqliteAuditWriterOptions>()
            .Bind(config.GetSection(SiteWriterSectionName));
        services.AddOptions<SiteAuditTelemetryOptions>()
            .Bind(config.GetSection(SiteTelemetrySectionName));

        // SqliteAuditWriter is a singleton with a single owned SqliteConnection
        // and a background writer Task; multiple instances would race on the
        // same file. Registered concretely so the ISiteAuditQueue + IAuditWriter
        // forwards below resolve to the same instance — the actor must observe
        // the writes made via the hot-path interface.
        services.AddSingleton<SqliteAuditWriter>();
        services.AddSingleton<ISiteAuditQueue>(sp => sp.GetRequiredService<SqliteAuditWriter>());

        // RingBufferFallback: drop-oldest in-memory ring used by
        // FallbackAuditWriter when the primary SQLite writer throws. Default
        // capacity is fine for M2 (1024).
        services.AddSingleton<RingBufferFallback>();

        // IAuditWriteFailureCounter: NoOp default. Bundle G overrides this
        // binding with the real Site Health Monitoring counter. Registered
        // before FallbackAuditWriter so the factory can resolve it.
        services.AddSingleton<IAuditWriteFailureCounter, NoOpAuditWriteFailureCounter>();

        // The script-thread surface is FallbackAuditWriter (primary + ring +
        // counter), not the raw SqliteAuditWriter — primary failures must NEVER
        // abort the user-facing action.
        services.AddSingleton<IAuditWriter>(sp => new FallbackAuditWriter(
            primary: sp.GetRequiredService<SqliteAuditWriter>(),
            ring: sp.GetRequiredService<RingBufferFallback>(),
            failureCounter: sp.GetRequiredService<IAuditWriteFailureCounter>(),
            logger: sp.GetRequiredService<ILogger<FallbackAuditWriter>>()));

        // ISiteStreamAuditClient: NoOp default. M6's reconciliation work brings
        // the real gRPC-backed implementation (no site→central gRPC channel
        // exists today — sites talk to central via Akka ClusterClient only).
        // Bundle H's integration test substitutes a stub directly into the
        // SiteAuditTelemetryActor's Props.Create call.
        services.AddSingleton<ISiteStreamAuditClient, NoOpSiteStreamAuditClient>();

        return services;
    }

    /// <summary>
    /// Audit Log (#23) M2 Bundle G — swap the default
    /// <see cref="NoOpAuditWriteFailureCounter"/> registration for the real
    /// <see cref="HealthMetricsAuditWriteFailureCounter"/> bridge so the
    /// FallbackAuditWriter primary-failure counter surfaces in the site health
    /// report payload as <c>SiteHealthReport.SiteAuditWriteFailures</c>.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Must be called AFTER both <see cref="AddAuditLog"/> (registers the
    /// NoOp default this method replaces) and
    /// <c>ScadaLink.HealthMonitoring.ServiceCollectionExtensions.AddHealthMonitoring</c>
    /// or <c>AddSiteHealthMonitoring</c> (registers the
    /// <see cref="ISiteHealthCollector"/> the bridge depends on). Resolving
    /// <see cref="IAuditWriteFailureCounter"/> without the latter throws
    /// <see cref="InvalidOperationException"/> at <c>GetRequiredService</c>
    /// time — by design, since a silent NoOp would mask a misconfiguration.
    /// </para>
    /// <para>
    /// Idempotent — calling twice replaces the descriptor each time without
    /// piling up registrations.
    /// </para>
    /// </remarks>
    public static IServiceCollection AddAuditLogHealthMetricsBridge(this IServiceCollection services)
    {
        ArgumentNullException.ThrowIfNull(services);

        services.Replace(
            ServiceDescriptor.Singleton<IAuditWriteFailureCounter, HealthMetricsAuditWriteFailureCounter>());
        return services;
    }
}
