using System.Text.Json;
using Microsoft.Extensions.Logging;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;

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

/// <summary>
///     Registers the MQTT / Sparkplug B driver with the <see cref="DriverFactoryRegistry"/>. The
///     Host's <c>DriverFactoryBootstrap</c> calls <see cref="Register"/> once at startup; the
///     driver-instance bootstrapper then materialises <c>DriverInstance</c> rows of type
///     <see cref="DriverTypeNames.Mqtt"/> into live <see cref="MqttDriver"/> instances.
/// </summary>
/// <remarks>
///     <para>
///         <b>Direct deserialization, no intermediate DTO.</b> The <c>DriverConfig</c> blob binds
///         straight onto <see cref="MqttDriverOptions"/> — the same shape
///         <see cref="MqttDriverProbe"/>, <c>MqttDriverBrowser</c> and
///         <see cref="MqttDriver.ReinitializeAsync"/> already bind, and the shape the options record
///         was designed for (every knob carries its own default, and <c>rawTags</c> is a plain
///         <see cref="RawTagEntry"/> list needing no translation). Mirrors
///         <c>OpcUaClientDriverFactoryExtensions</c>. <c>ModbusDriverFactoryExtensions</c>' separate
///         DTO exists to service a legacy nullable-everything blob plus string→enum parsing this
///         driver does with a converter instead; copying it here would add a <i>fourth</i> parse
///         shape for one config, which is precisely the divergence
///         <see cref="MqttJson.Options"/> exists to prevent.
///     </para>
///     <para>
///         <b>Connection-free.</b> <see cref="CreateInstance"/> parses and constructs only — the
///         <see cref="MqttDriver"/> constructor touches no network, and the registry contract
///         forbids the factory from calling <c>InitializeAsync</c> itself (the driver host owns the
///         retry semantics).
///     </para>
/// </remarks>
public static class MqttDriverFactoryExtensions
{
    /// <summary>
    ///     Driver type name — matches <c>DriverInstance.DriverType</c> values. Sourced from
    ///     <see cref="DriverTypeNames.Mqtt"/> so the registration key can never drift from the
    ///     constant the dispatch maps, the probe and the browser reference.
    /// </summary>
    public const string DriverTypeName = DriverTypeNames.Mqtt;

    /// <summary>
    ///     Register the MQTT factory with the driver registry. The optional
    ///     <paramref name="loggerFactory"/> is captured at registration time and used to construct an
    ///     <see cref="ILogger{MqttDriver}"/> per driver instance — without it the driver runs with no
    ///     logger (standalone/test callers stay unchanged).
    /// </summary>
    /// <param name="registry">The driver factory registry to register with.</param>
    /// <param name="loggerFactory">Optional logger factory used to create per-instance loggers.</param>
    public static void Register(DriverFactoryRegistry registry, ILoggerFactory? loggerFactory = null)
    {
        ArgumentNullException.ThrowIfNull(registry);
        registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory));
    }

    /// <summary>Public for the Server-side bootstrapper + test consumers.</summary>
    /// <param name="driverInstanceId">Stable logical id of the driver instance.</param>
    /// <param name="driverConfigJson">The <c>DriverConfig</c> JSON blob for the instance.</param>
    /// <param name="loggerFactory">Optional logger factory for the per-instance logger.</param>
    /// <returns>A configured, not-yet-connected <see cref="MqttDriver"/>.</returns>
    /// <exception cref="ArgumentException">
    ///     <paramref name="driverInstanceId"/> or <paramref name="driverConfigJson"/> is blank.
    /// </exception>
    /// <exception cref="InvalidOperationException">The config blob deserialised to <c>null</c>.</exception>
    /// <exception cref="JsonException">The config blob is not valid JSON for the options shape.</exception>
    public static MqttDriver CreateInstance(
        string driverInstanceId,
        string driverConfigJson,
        ILoggerFactory? loggerFactory = null)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
        ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);

        // MqttJson.Options — the one shared instance (see its remarks). A local copy here is the
        // repo's documented systemic enum bug in the making.
        var options = JsonSerializer.Deserialize<MqttDriverOptions>(driverConfigJson, MqttJson.Options)
            ?? throw new InvalidOperationException(
                $"MQTT driver config for '{driverInstanceId}' deserialised to null");

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