using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;
using ZB.MOM.WW.Secrets.Abstractions;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy;

/// <summary>
///     Static factory registration helper for <see cref="GalaxyDriver"/>. Mirrors
///     <c>GalaxyProxyDriverFactoryExtensions</c> / <c>ModbusDriverFactoryExtensions</c>.
///     Server's <c>Program.cs</c> calls <see cref="Register"/> once at startup; the driver
///     bootstrap pipeline materialises DriverInstance rows whose <c>DriverType</c> matches
///     <see cref="DriverTypeName"/> into live <see cref="GalaxyDriver"/> instances.
/// </summary>
/// <remarks>
///     <c>"GalaxyMxGateway"</c> is the sole Galaxy driver-type name. The legacy
///     <c>"Galaxy"</c> proxy type (retired in PR 7.2) no longer exists; there is no
///     server-side backend switch — every Galaxy DriverInstance row uses this type name.
/// </remarks>
public static class GalaxyDriverFactoryExtensions
{
    public const string DriverTypeName = "GalaxyMxGateway";

    /// <summary>Registers the Galaxy driver factory with the given registry, secret resolver, and optional logger factory.</summary>
    /// <param name="registry">The driver factory registry to register with.</param>
    /// <param name="secretResolver">
    ///     The shared secret resolver (from DI) used to resolve the <c>secret:</c> arm of
    ///     each instance's <c>Gateway.ApiKeySecretRef</c>.
    /// </param>
    /// <param name="loggerFactory">The optional logger factory for creating drivers.</param>
    public static void Register(
        DriverFactoryRegistry registry,
        ISecretResolver secretResolver,
        ILoggerFactory? loggerFactory = null)
    {
        ArgumentNullException.ThrowIfNull(registry);
        ArgumentNullException.ThrowIfNull(secretResolver);
        registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory, secretResolver));
    }

    /// <summary>
    ///     Convenience for tests + standalone callers. Uses the <see cref="NullSecretResolver"/>
    ///     null-object, so the <c>secret:</c> API-key arm resolves fail-closed (absent) — callers
    ///     that need a working <c>secret:</c> ref must use the <see cref="Register"/> path that
    ///     threads the DI resolver.
    /// </summary>
    /// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
    /// <param name="driverConfigJson">The driver configuration in JSON format.</param>
    /// <returns>The constructed Galaxy driver instance.</returns>
    public static GalaxyDriver CreateInstance(string driverInstanceId, string driverConfigJson)
        => CreateInstance(driverInstanceId, driverConfigJson, loggerFactory: null, NullSecretResolver.Instance);

    /// <summary>Creates a Galaxy driver instance from configuration JSON with optional logger factory and a secret resolver.</summary>
    /// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
    /// <param name="driverConfigJson">The driver configuration in JSON format.</param>
    /// <param name="loggerFactory">The optional logger factory for creating drivers.</param>
    /// <param name="secretResolver">The secret resolver for the driver's <c>secret:</c> API-key arm.</param>
    /// <returns>The constructed Galaxy driver instance.</returns>
    public static GalaxyDriver CreateInstance(
        string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory, ISecretResolver secretResolver)
    {
        ArgumentNullException.ThrowIfNull(secretResolver);
        ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
        ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);

        var dto = JsonSerializer.Deserialize<GalaxyDriverConfigDto>(driverConfigJson, JsonOptions)
            ?? throw new InvalidOperationException(
                $"Galaxy driver config for '{driverInstanceId}' deserialised to null");

        var options = new GalaxyDriverOptions(
            Gateway: new GalaxyGatewayOptions(
                Endpoint: dto.Gateway?.Endpoint
                    ?? throw new InvalidOperationException(
                        $"Galaxy driver '{driverInstanceId}' missing required Gateway.Endpoint"),
                ApiKeySecretRef: dto.Gateway.ApiKeySecretRef
                    ?? throw new InvalidOperationException(
                        $"Galaxy driver '{driverInstanceId}' missing required Gateway.ApiKeySecretRef"),
                UseTls: dto.Gateway.UseTls ?? true,
                CaCertificatePath: dto.Gateway.CaCertificatePath,
                ConnectTimeoutSeconds: dto.Gateway.ConnectTimeoutSeconds ?? 10,
                DefaultCallTimeoutSeconds: dto.Gateway.DefaultCallTimeoutSeconds ?? 30,
                StreamTimeoutSeconds: dto.Gateway.StreamTimeoutSeconds ?? 0),
            MxAccess: new GalaxyMxAccessOptions(
                ClientName: dto.MxAccess?.ClientName
                    ?? throw new InvalidOperationException(
                        $"Galaxy driver '{driverInstanceId}' missing required MxAccess.ClientName"),
                PublishingIntervalMs: dto.MxAccess.PublishingIntervalMs ?? 1000,
                WriteUserId: dto.MxAccess.WriteUserId ?? 0,
                EventPumpChannelCapacity: dto.MxAccess.EventPumpChannelCapacity ?? 50_000),
            Repository: new GalaxyRepositoryOptions(
                DiscoverPageSize: dto.Repository?.DiscoverPageSize ?? 5000,
                WatchDeployEvents: dto.Repository?.WatchDeployEvents ?? true),
            Reconnect: new GalaxyReconnectOptions(
                InitialBackoffMs: dto.Reconnect?.InitialBackoffMs ?? 500,
                MaxBackoffMs: dto.Reconnect?.MaxBackoffMs ?? 30_000,
                ReplayOnSessionLost: dto.Reconnect?.ReplayOnSessionLost ?? true))
        {
            // v3: the deploy artifact delivers the authored raw tags (RawPath identity + TagConfig blob
            // carrying the camelCase "attributeRef" + WriteIdempotent flag). The driver builds its
            // RawPath → attributeRef table from these. Empty when the config omits them.
            RawTags = dto.RawTags ?? [],
        };

        return new GalaxyDriver(driverInstanceId, options, secretResolver, loggerFactory?.CreateLogger<GalaxyDriver>());
    }

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        ReadCommentHandling = JsonCommentHandling.Skip,
        AllowTrailingCommas = true,
    };

    /// <summary>Data transfer object for Galaxy driver configuration JSON.</summary>
    internal sealed class GalaxyDriverConfigDto
    {
        /// <summary>Gets or sets the gateway configuration.</summary>
        public GatewayDto? Gateway { get; init; }
        /// <summary>Gets or sets the MX Access configuration.</summary>
        public MxAccessDto? MxAccess { get; init; }
        /// <summary>Gets or sets the repository configuration.</summary>
        public RepositoryDto? Repository { get; init; }
        /// <summary>Gets or sets the reconnect configuration.</summary>
        public ReconnectDto? Reconnect { get; init; }
        /// <summary>
        ///     Gets or sets the v3 authored raw tags. Each <see cref="RawTagEntry"/> carries the RawPath
        ///     identity, the driver <c>TagConfig</c> blob (with the Galaxy <c>attributeRef</c>), and the
        ///     WriteIdempotent flag. Bound straight through to <see cref="GalaxyDriverOptions.RawTags"/>.
        /// </summary>
        public List<RawTagEntry>? RawTags { get; init; }
    }

    /// <summary>Gateway configuration section.</summary>
    internal sealed class GatewayDto
    {
        /// <summary>Gets or sets the gateway endpoint address.</summary>
        public string? Endpoint { get; init; }
        /// <summary>Gets or sets the API key secret reference.</summary>
        public string? ApiKeySecretRef { get; init; }
        /// <summary>Gets or sets whether to use TLS.</summary>
        public bool? UseTls { get; init; }
        /// <summary>Gets or sets the CA certificate path.</summary>
        public string? CaCertificatePath { get; init; }
        /// <summary>Gets or sets the connection timeout in seconds.</summary>
        public int? ConnectTimeoutSeconds { get; init; }
        /// <summary>Gets or sets the default call timeout in seconds.</summary>
        public int? DefaultCallTimeoutSeconds { get; init; }
        /// <summary>Gets or sets the stream timeout in seconds.</summary>
        public int? StreamTimeoutSeconds { get; init; }
    }

    /// <summary>MX Access configuration section.</summary>
    internal sealed class MxAccessDto
    {
        /// <summary>Gets or sets the client name.</summary>
        public string? ClientName { get; init; }
        /// <summary>Gets or sets the publishing interval in milliseconds.</summary>
        public int? PublishingIntervalMs { get; init; }
        /// <summary>Gets or sets the write user ID.</summary>
        public int? WriteUserId { get; init; }
        /// <summary>Gets or sets the event pump channel capacity.</summary>
        public int? EventPumpChannelCapacity { get; init; }
    }

    /// <summary>Repository configuration section.</summary>
    internal sealed class RepositoryDto
    {
        /// <summary>Gets or sets the discover page size.</summary>
        public int? DiscoverPageSize { get; init; }
        /// <summary>Gets or sets whether to watch deploy events.</summary>
        public bool? WatchDeployEvents { get; init; }
    }

    /// <summary>Reconnect configuration section.</summary>
    internal sealed class ReconnectDto
    {
        /// <summary>Gets or sets the initial backoff in milliseconds.</summary>
        public int? InitialBackoffMs { get; init; }
        /// <summary>Gets or sets the maximum backoff in milliseconds.</summary>
        public int? MaxBackoffMs { get; init; }
        /// <summary>Gets or sets whether to replay on session lost.</summary>
        public bool? ReplayOnSessionLost { get; init; }
    }
}
