using System.Collections.Generic;
using System.Linq;
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.OpcUaClient;

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

public sealed class OpcUaClientDriverPageFormSerializationTests
{
    private static readonly JsonSerializerOptions _opts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = false,
    };

    [Fact]
    public void RoundTrip_PreservesKnownFields()
    {
        var original = new OpcUaClientDriverOptions
        {
            EndpointUrl = "opc.tcp://plc.internal:4840",
            ApplicationUri = "urn:plc:OtOpcUa:GatewayClient",
            SessionName = "MySession",
            SecurityMode = OpcUaSecurityMode.SignAndEncrypt,
            SecurityPolicy = OpcUaSecurityPolicy.Basic256Sha256,
            AuthType = OpcUaAuthType.Username,
            Username = "operator",
            Password = "s3cr3t",
            PerEndpointConnectTimeout = TimeSpan.FromSeconds(5),
            Timeout = TimeSpan.FromSeconds(20),
            SessionTimeout = TimeSpan.FromSeconds(180),
            KeepAliveInterval = TimeSpan.FromSeconds(10),
            ReconnectPeriod = TimeSpan.FromSeconds(15),
            AutoAcceptCertificates = true,
            BrowseRoot = "i=85",
            MaxDiscoveredNodes = 5000,
            MaxBrowseDepth = 6,
            TargetNamespaceKind = OpcUaTargetNamespaceKind.SystemPlatform,
            ProbeTimeoutSeconds = 20,
        };

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

        back.ShouldNotBeNull();
        back.EndpointUrl.ShouldBe("opc.tcp://plc.internal:4840");
        back.ApplicationUri.ShouldBe("urn:plc:OtOpcUa:GatewayClient");
        back.SessionName.ShouldBe("MySession");
        back.SecurityMode.ShouldBe(OpcUaSecurityMode.SignAndEncrypt);
        back.SecurityPolicy.ShouldBe(OpcUaSecurityPolicy.Basic256Sha256);
        back.AuthType.ShouldBe(OpcUaAuthType.Username);
        back.Username.ShouldBe("operator");
        back.Password.ShouldBe("s3cr3t");
        back.PerEndpointConnectTimeout.ShouldBe(TimeSpan.FromSeconds(5));
        back.Timeout.ShouldBe(TimeSpan.FromSeconds(20));
        back.SessionTimeout.ShouldBe(TimeSpan.FromSeconds(180));
        back.KeepAliveInterval.ShouldBe(TimeSpan.FromSeconds(10));
        back.ReconnectPeriod.ShouldBe(TimeSpan.FromSeconds(15));
        back.AutoAcceptCertificates.ShouldBeTrue();
        back.BrowseRoot.ShouldBe("i=85");
        back.MaxDiscoveredNodes.ShouldBe(5000);
        back.MaxBrowseDepth.ShouldBe(6);
        back.TargetNamespaceKind.ShouldBe(OpcUaTargetNamespaceKind.SystemPlatform);
        back.ProbeTimeoutSeconds.ShouldBe(20);
    }

    [Fact]
    public void Deserialize_DropsUnknownFields()
    {
        var jsonWithExtra = """{"unknownField":"old-value","probeTimeoutSeconds":20}""";

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

        var back = JsonSerializer.Deserialize<OpcUaClientDriverOptions>(jsonWithExtra, optsWithSkip);
        back.ShouldNotBeNull();
        back.ProbeTimeoutSeconds.ShouldBe(20);
    }

    [Fact]
    public void FormModel_RoundTrip_PreservesAllFields()
    {
        // Construct options with non-default values for every editable property plus a
        // non-empty UnsMappingTable (read-only in the form, round-tripped via the original
        // record). EndpointUrls is now edited via the CollectionEditor on the page and is
        // threaded into ToRecord explicitly; see EndpointUrls_ListRoundTrip_PreservesOrder.
        var endpointUrls = new List<string> { "opc.tcp://primary:4840", "opc.tcp://backup:4840" };
        var unsMappingTable = new Dictionary<string, string>
        {
            ["Line1/"] = "Site/Area1/Line1",
            ["Line2/"] = "Site/Area1/Line2",
        };

        var original = new OpcUaClientDriverOptions
        {
            EndpointUrl              = "opc.tcp://fallback:4840",
            EndpointUrls             = endpointUrls,
            ApplicationUri           = "urn:test:OtOpcUa:GatewayClient",
            SessionName              = "TestSession",
            SecurityMode             = OpcUaSecurityMode.SignAndEncrypt,
            SecurityPolicy           = OpcUaSecurityPolicy.Basic256Sha256,
            AuthType                 = OpcUaAuthType.Username,
            Username                 = "opcuser",
            Password                 = "p@ssw0rd",
            UserCertificatePath      = @"C:\certs\user.pfx",
            UserCertificatePassword  = "certpass",
            PerEndpointConnectTimeout = TimeSpan.FromSeconds(4),
            Timeout                  = TimeSpan.FromSeconds(12),
            SessionTimeout           = TimeSpan.FromSeconds(150),
            KeepAliveInterval        = TimeSpan.FromSeconds(7),
            ReconnectPeriod          = TimeSpan.FromSeconds(8),
            AutoAcceptCertificates   = true,
            BrowseRoot               = "i=85",
            MaxDiscoveredNodes       = 3000,
            MaxBrowseDepth           = 5,
            TargetNamespaceKind      = OpcUaTargetNamespaceKind.SystemPlatform,
            UnsMappingTable          = unsMappingTable,
            ProbeTimeoutSeconds      = 25,
        };

        var form = OpcUaClientDriverForm.OpcUaClientFormModel.FromRecord(original);
        var result = form.ToRecord(endpointUrls);

        result.EndpointUrl.ShouldBe("opc.tcp://fallback:4840");
        result.EndpointUrls.Count.ShouldBe(2);
        result.EndpointUrls[0].ShouldBe("opc.tcp://primary:4840");
        result.EndpointUrls[1].ShouldBe("opc.tcp://backup:4840");
        result.ApplicationUri.ShouldBe("urn:test:OtOpcUa:GatewayClient");
        result.SessionName.ShouldBe("TestSession");
        result.SecurityMode.ShouldBe(OpcUaSecurityMode.SignAndEncrypt);
        result.SecurityPolicy.ShouldBe(OpcUaSecurityPolicy.Basic256Sha256);
        result.AuthType.ShouldBe(OpcUaAuthType.Username);
        result.Username.ShouldBe("opcuser");
        result.Password.ShouldBe("p@ssw0rd");
        result.UserCertificatePath.ShouldBe(@"C:\certs\user.pfx");
        result.UserCertificatePassword.ShouldBe("certpass");
        result.PerEndpointConnectTimeout.ShouldBe(TimeSpan.FromSeconds(4));
        result.Timeout.ShouldBe(TimeSpan.FromSeconds(12));
        result.SessionTimeout.ShouldBe(TimeSpan.FromSeconds(150));
        result.KeepAliveInterval.ShouldBe(TimeSpan.FromSeconds(7));
        result.ReconnectPeriod.ShouldBe(TimeSpan.FromSeconds(8));
        result.AutoAcceptCertificates.ShouldBeTrue();
        result.BrowseRoot.ShouldBe("i=85");
        result.MaxDiscoveredNodes.ShouldBe(3000);
        result.MaxBrowseDepth.ShouldBe(5);
        result.TargetNamespaceKind.ShouldBe(OpcUaTargetNamespaceKind.SystemPlatform);
        result.UnsMappingTable.Count.ShouldBe(2);
        result.UnsMappingTable["Line1/"].ShouldBe("Site/Area1/Line1");
        result.UnsMappingTable["Line2/"].ShouldBe("Site/Area1/Line2");
        result.ProbeTimeoutSeconds.ShouldBe(25);
    }

    [Fact]
    public void EndpointUrlRow_FromUrl_ToUrl_Trims()
    {
        var row = OpcUaClientDriverForm.EndpointUrlRow.FromUrl("  opc.tcp://plc:4840  ");

        row.Url.ShouldBe("  opc.tcp://plc:4840  ");
        row.ToUrl().ShouldBe("opc.tcp://plc:4840");
    }

    [Fact]
    public void EndpointUrlRow_ValidateRow_RejectsBlank()
    {
        var all = new List<OpcUaClientDriverForm.EndpointUrlRow>();
        var row = new OpcUaClientDriverForm.EndpointUrlRow { Url = "   " };

        var error = OpcUaClientDriverForm.EndpointUrlRow.ValidateRow(row, all, null);

        error.ShouldBe("URL is required.");
    }

    [Fact]
    public void EndpointUrlRow_ValidateRow_RejectsNonOpcTcpScheme()
    {
        var all = new List<OpcUaClientDriverForm.EndpointUrlRow>();
        var row = new OpcUaClientDriverForm.EndpointUrlRow { Url = "http://plc:4840" };

        var error = OpcUaClientDriverForm.EndpointUrlRow.ValidateRow(row, all, null);

        error.ShouldBe("Endpoint URL must start with opc.tcp://");
    }

    [Fact]
    public void EndpointUrlRow_ValidateRow_RejectsDuplicate()
    {
        var all = new List<OpcUaClientDriverForm.EndpointUrlRow>
        {
            new() { Url = "opc.tcp://primary:4840" },
            new() { Url = "opc.tcp://backup:4840" },
        };
        // Adding a new row (editIndex null) duplicating the first — case-insensitive, whitespace-insensitive.
        var row = new OpcUaClientDriverForm.EndpointUrlRow { Url = "  OPC.TCP://primary:4840 " };

        var error = OpcUaClientDriverForm.EndpointUrlRow.ValidateRow(row, all, null);

        error.ShouldNotBeNull();
        error.ShouldContain("Duplicate endpoint");
    }

    [Fact]
    public void EndpointUrlRow_ValidateRow_AllowsEditingRowInPlace()
    {
        var all = new List<OpcUaClientDriverForm.EndpointUrlRow>
        {
            new() { Url = "opc.tcp://primary:4840" },
            new() { Url = "opc.tcp://backup:4840" },
        };
        // Editing index 0 and keeping the same URL must not flag itself as a duplicate.
        var row = new OpcUaClientDriverForm.EndpointUrlRow { Url = "opc.tcp://primary:4840" };

        var error = OpcUaClientDriverForm.EndpointUrlRow.ValidateRow(row, all, 0);

        error.ShouldBeNull();
    }

    [Fact]
    public void EndpointUrls_ListRoundTrip_PreservesOrder()
    {
        // The page holds endpoints as a List<EndpointUrlRow>; loading from EndpointUrls and
        // converting back must preserve order (the failover list is ordered, primary first).
        var endpointUrls = new List<string> { "opc.tcp://primary:4840", "opc.tcp://secondary:4840", "opc.tcp://tertiary:4840" };

        var rows = endpointUrls
            .Select(OpcUaClientDriverForm.EndpointUrlRow.FromUrl)
            .ToList();
        var roundTripped = rows.Select(r => r.ToUrl()).ToList();

        roundTripped.Count.ShouldBe(3);
        roundTripped[0].ShouldBe("opc.tcp://primary:4840");
        roundTripped[1].ShouldBe("opc.tcp://secondary:4840");
        roundTripped[2].ShouldBe("opc.tcp://tertiary:4840");

        var form = OpcUaClientDriverForm.OpcUaClientFormModel.FromRecord(new OpcUaClientDriverOptions());
        var result = form.ToRecord(roundTripped);
        result.EndpointUrls.Count.ShouldBe(3);
        result.EndpointUrls[0].ShouldBe("opc.tcp://primary:4840");
        result.EndpointUrls[2].ShouldBe("opc.tcp://tertiary:4840");
    }
}
