using System.Text.Json;
using System.Text.Json.Serialization;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Forms;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Config;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests;

public sealed class GalaxyDriverPageFormSerializationTests
{
    // Matches GalaxyDriverForm._jsonOpts (camelCase, no PropertyNameCaseInsensitive).
    private static readonly JsonSerializerOptions _opts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = false,
    };

    // Matches the page's _jsonOpts exactly: camelCase + case-insensitive read + UnmappedMemberHandling.Skip.
    private static readonly JsonSerializerOptions _pageOpts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true,
        UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
        WriteIndented = false,
    };

    [Fact]
    public void RoundTrip_PreservesKnownFields()
    {
        var original = new GalaxyDriverOptions(
            Gateway: new GalaxyGatewayOptions(
                Endpoint: "https://gw.internal:5001",
                ApiKeySecretRef: "env:MY_API_KEY",
                UseTls: true,
                CaCertificatePath: "C:\\certs\\ca.pem",
                ConnectTimeoutSeconds: 15,
                DefaultCallTimeoutSeconds: 45,
                StreamTimeoutSeconds: 0),
            MxAccess: new GalaxyMxAccessOptions(
                ClientName: "OtOpcUa-Primary",
                PublishingIntervalMs: 500,
                WriteUserId: 1,
                EventPumpChannelCapacity: 100_000),
            Repository: new GalaxyRepositoryOptions(
                DiscoverPageSize: 2000,
                WatchDeployEvents: false),
            Reconnect: new GalaxyReconnectOptions(
                InitialBackoffMs: 1000,
                MaxBackoffMs: 60_000,
                ReplayOnSessionLost: false))
        {
            ProbeTimeoutSeconds = 45,
        };

        var json = JsonSerializer.Serialize(original, _opts);
        var back = JsonSerializer.Deserialize<GalaxyDriverOptions>(json, _opts);

        back.ShouldNotBeNull();
        back.Gateway.Endpoint.ShouldBe("https://gw.internal:5001");
        back.Gateway.ApiKeySecretRef.ShouldBe("env:MY_API_KEY");
        back.Gateway.UseTls.ShouldBeTrue();
        back.Gateway.CaCertificatePath.ShouldBe("C:\\certs\\ca.pem");
        back.Gateway.ConnectTimeoutSeconds.ShouldBe(15);
        back.Gateway.DefaultCallTimeoutSeconds.ShouldBe(45);
        back.Gateway.StreamTimeoutSeconds.ShouldBe(0);
        back.MxAccess.ClientName.ShouldBe("OtOpcUa-Primary");
        back.MxAccess.PublishingIntervalMs.ShouldBe(500);
        back.MxAccess.WriteUserId.ShouldBe(1);
        back.MxAccess.EventPumpChannelCapacity.ShouldBe(100_000);
        back.Repository.DiscoverPageSize.ShouldBe(2000);
        back.Repository.WatchDeployEvents.ShouldBeFalse();
        back.Reconnect.InitialBackoffMs.ShouldBe(1000);
        back.Reconnect.MaxBackoffMs.ShouldBe(60_000);
        back.Reconnect.ReplayOnSessionLost.ShouldBeFalse();
        back.ProbeTimeoutSeconds.ShouldBe(45);
    }

    [Fact]
    public void Deserialize_DropsUnknownFields()
    {
        // Minimal JSON that sets only ProbeTimeoutSeconds and an unknown field.
        // The nested records must supply their required positional args for JSON deserialization
        // to succeed — provide them here so the test exercises the Unknown-field skip path.
        var jsonWithExtra = """
            {
              "unknownField": "old-value",
              "gateway": { "endpoint": "https://localhost:5001", "apiKeySecretRef": "dev:test" },
              "mxAccess": { "clientName": "OtOpcUa" },
              "repository": {},
              "reconnect": {},
              "probeTimeoutSeconds": 20
            }
            """;

        var optsWithSkip = new JsonSerializerOptions(_opts)
        {
            UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
        };

        var back = JsonSerializer.Deserialize<GalaxyDriverOptions>(jsonWithExtra, optsWithSkip);
        back.ShouldNotBeNull();
        back.ProbeTimeoutSeconds.ShouldBe(20);
        back.Gateway.Endpoint.ShouldBe("https://localhost:5001");
    }

    /// <summary>
    ///     Regression test: the seed SQL stores PascalCase JSON. With
    ///     <c>PropertyNameCaseInsensitive = true</c> the page must read the real values, not
    ///     fall back to defaults. FAILS against case-sensitive opts; PASSES with the fix.
    /// </summary>
    [Fact]
    public void Seeded_pascalcase_config_loads_real_values()
    {
        // Exact JSON from docker-dev/seed/seed-clusters.sql (lines 130-151).
        var seededJson = """
            {
              "Gateway": {
                "Endpoint": "http://10.100.0.48:5120",
                "ApiKeySecretRef": "env:GALAXY_MXGW_API_KEY",
                "UseTls": false,
                "ConnectTimeoutSeconds": 10,
                "DefaultCallTimeoutSeconds": 30
              },
              "MxAccess": {
                "ClientName": "OtOpcUa-MAIN-docker-dev",
                "PublishingIntervalMs": 1000
              },
              "Repository": {
                "DiscoverPageSize": 5000,
                "WatchDeployEvents": true
              },
              "Reconnect": {
                "InitialBackoffMs": 500,
                "MaxBackoffMs": 30000,
                "ReplayOnSessionLost": true
              }
            }
            """;

        // Deserialize with page-mirrored opts (camelCase + case-insensitive, as fixed).
        var driverOpts = JsonSerializer.Deserialize<GalaxyDriverOptions>(seededJson, _pageOpts);
        driverOpts.ShouldNotBeNull();

        var form = GalaxyDriverForm.GalaxyFormModel.FromRecord(driverOpts!);

        // Assert REAL seeded values — not defaults.
        form.GatewayEndpoint.ShouldBe("http://10.100.0.48:5120");
        form.GatewayApiKeySecretRef.ShouldBe("env:GALAXY_MXGW_API_KEY");
        form.GatewayUseTls.ShouldBeFalse();
        form.MxClientName.ShouldBe("OtOpcUa-MAIN-docker-dev");
        form.RepositoryDiscoverPageSize.ShouldBe(5000);
        form.ReconnectInitialBackoffMs.ShouldBe(500);
    }

    /// <summary>
    ///     Defence-in-depth: a config that genuinely OMITS a section (no Reconnect key at all)
    ///     must not throw — <see cref="GalaxyDriverForm.GalaxyFormModel.FromRecord"/> must
    ///     null-coalesce the missing section to its default value.
    /// </summary>
    [Fact]
    public void FromRecord_with_omitted_section_uses_defaults()
    {
        // Only gateway section present — Reconnect intentionally absent.
        var partialJson = """
            {
              "gateway": {
                "endpoint": "opc://x",
                "apiKeySecretRef": "env:K"
              }
            }
            """;

        var driverOpts = JsonSerializer.Deserialize<GalaxyDriverOptions>(partialJson, _pageOpts);
        driverOpts.ShouldNotBeNull();

        // FromRecord must not throw even though Reconnect (and other sections) is null.
        var form = Should.NotThrow(() => GalaxyDriverForm.GalaxyFormModel.FromRecord(driverOpts!));

        // Omitted Reconnect section falls back to GalaxyReconnectOptions() defaults.
        var defaultRc = new GalaxyReconnectOptions();
        form.ReconnectInitialBackoffMs.ShouldBe(defaultRc.InitialBackoffMs);
        form.ReconnectMaxBackoffMs.ShouldBe(defaultRc.MaxBackoffMs);
        form.ReconnectReplayOnSessionLost.ShouldBe(defaultRc.ReplayOnSessionLost);
    }

    /// <summary>
    ///     Confirms that <see cref="GalaxyDriverForm.GalaxyFormModel.FromRecord"/> still
    ///     round-trips correctly when all nested records are populated (non-regressed path).
    /// </summary>
    [Fact]
    public void FromRecord_with_fully_populated_options_round_trips()
    {
        var original = new GalaxyDriverOptions(
            Gateway: new GalaxyGatewayOptions(
                Endpoint: "https://gw.example.com:5001",
                ApiKeySecretRef: "env:MY_KEY",
                UseTls: true,
                CaCertificatePath: null,
                ConnectTimeoutSeconds: 12,
                DefaultCallTimeoutSeconds: 40,
                StreamTimeoutSeconds: 0),
            MxAccess: new GalaxyMxAccessOptions(
                ClientName: "OtOpcUa-Test",
                PublishingIntervalMs: 750,
                WriteUserId: 2,
                EventPumpChannelCapacity: 25_000),
            Repository: new GalaxyRepositoryOptions(
                DiscoverPageSize: 3000,
                WatchDeployEvents: false),
            Reconnect: new GalaxyReconnectOptions(
                InitialBackoffMs: 800,
                MaxBackoffMs: 45_000,
                ReplayOnSessionLost: false))
        {
            ProbeTimeoutSeconds = 20,
        };

        var form = GalaxyDriverForm.GalaxyFormModel.FromRecord(original);

        form.GatewayEndpoint.ShouldBe("https://gw.example.com:5001");
        form.GatewayApiKeySecretRef.ShouldBe("env:MY_KEY");
        form.GatewayUseTls.ShouldBeTrue();
        form.GatewayConnectTimeoutSeconds.ShouldBe(12);
        form.GatewayDefaultCallTimeoutSeconds.ShouldBe(40);
        form.MxClientName.ShouldBe("OtOpcUa-Test");
        form.MxPublishingIntervalMs.ShouldBe(750);
        form.MxWriteUserId.ShouldBe(2);
        form.MxEventPumpChannelCapacity.ShouldBe(25_000);
        form.RepositoryDiscoverPageSize.ShouldBe(3000);
        form.RepositoryWatchDeployEvents.ShouldBeFalse();
        form.ReconnectInitialBackoffMs.ShouldBe(800);
        form.ReconnectMaxBackoffMs.ShouldBe(45_000);
        form.ReconnectReplayOnSessionLost.ShouldBeFalse();
        form.ProbeTimeoutSeconds.ShouldBe(20);
    }
}
