using ZB.MOM.WW.OtOpcUa.AdminUI.Uns.TagEditors;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.DeviceForms;

/// <summary>
///     Device model for the MQTT / Sparkplug B driver. An MQTT driver instance holds exactly <b>one</b>
///     broker session, configured on the <b>driver</b> (<c>MqttDriverForm</c>) — so, like Galaxy, there
///     is no per-device connection endpoint to author and this model round-trips the <c>DeviceConfig</c>
///     JSON verbatim. Devices under an MQTT driver are pure organisational grouping in the RawPath.
/// </summary>
/// <remarks>
///     <para>
///     <b>Why the connection is not authored here.</b> <c>DriverDeviceConfigMerger</c> merges a device's
///     keys up to the top level <i>only when the driver has exactly one device</i>. A broker connection
///     authored on the device would therefore disappear from the merged blob the moment an operator adds
///     a second device — a silent, deploy-time-invisible outage. Driver-level authoring is correct for
///     0, 1 or N devices.
///     </para>
///     <para>
///     <b><see cref="LegacyConnectionKeys"/> exists because that merge-up still happens.</b> A
///     pre-form deployment (hand-authored or SQL-seeded, as the P1 live gate had to do) may carry
///     <c>Host</c>/<c>Port</c>/… on a sole device's <c>DeviceConfig</c>, and those keys keep winning over
///     the driver form's values. They are preserved (never silently dropped) and reported so the device
///     form can tell the operator why the driver form's host is not the one in effect.
///     </para>
/// </remarks>
public sealed class MqttDeviceModel
{
    /// <summary>
    ///     <c>MqttDriverOptions</c> connection-surface key names that a legacy <c>DeviceConfig</c> may
    ///     carry and that would merge up over the driver form's values.
    /// </summary>
    private static readonly string[] ConnectionKeyNames =
    [
        "Host", "Port", "ClientId", "UseTls", "AllowUntrustedServerCertificate",
        "CaCertificatePath", "Username", "Password", "ProtocolVersion", "CleanSession",
        "KeepAliveSeconds", "ConnectTimeoutSeconds", "ReconnectMinBackoffSeconds",
        "ReconnectMaxBackoffSeconds",
    ];

    private System.Text.Json.Nodes.JsonObject _bag = new();

    /// <summary>
    ///     The connection keys this <c>DeviceConfig</c> actually carries, in the casing they were
    ///     authored with — empty for the normal case. Non-empty means a sole-device merge-up will
    ///     override the driver form for those keys.
    /// </summary>
    public IReadOnlyList<string> LegacyConnectionKeys { get; private set; } = [];

    /// <summary>Loads a model from a <c>DeviceConfig</c> JSON string, retaining every original key.</summary>
    /// <param name="json">The raw <c>DeviceConfig</c> JSON string, or <c>null</c> for a new/empty device.</param>
    /// <returns>The populated <see cref="MqttDeviceModel"/>.</returns>
    public static MqttDeviceModel FromJson(string? json)
    {
        var bag = TagConfigJson.ParseOrNew(json);
        return new MqttDeviceModel
        {
            _bag = bag,
            LegacyConnectionKeys = bag
                .Select(p => p.Key)
                .Where(k => ConnectionKeyNames.Contains(k, StringComparer.OrdinalIgnoreCase))
                .ToList(),
        };
    }

    /// <summary>Serialises the (preserved) <c>DeviceConfig</c> back to a JSON string.</summary>
    /// <returns>The serialised <c>DeviceConfig</c> JSON string.</returns>
    public string ToJson() => TagConfigJson.Serialize(_bag);

    /// <summary>Validation hook; MQTT device rows carry no endpoint, so always valid.</summary>
    /// <returns><c>null</c> — no per-device endpoint to validate.</returns>
    public string? Validate() => null;
}
