using System.Text.Json;
using System.Text.Json.Serialization;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Sql;
using ZB.MOM.WW.OtOpcUa.Driver.Sql.Contracts;

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

/// <summary>
/// Guards the AdminUI <c>SqlDriverForm</c>'s driver-config output against the runtime factory that consumes
/// it. The form is razor (no bUnit here), so these tests pin two things the live verify then confirms:
/// (1) the exact JSON bytes the form emits for a minimal + fuller config are accepted by the authoritative
/// reader, <see cref="SqlDriverFactoryExtensions.CreateInstance(string,string)"/>; and (2) the
/// serialization contract — <c>provider</c> as a NAME string (never an ordinal), camelCase keys, and no
/// composer-owned <c>rawTags</c> / inert <c>allowWrites</c> leaking into the authored blob. A numeric enum
/// or a stray connection string is the "authors fine, never reads / leaks a secret" defect class this guards.
/// </summary>
public sealed class SqlDriverFormContractTests
{
    // The JsonSerializerOptions SqlDriverForm serializes with (camelCase + string enums + omit-nulls).
    private static readonly JsonSerializerOptions FormOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        WriteIndented = false,
        Converters = { new JsonStringEnumConverter() },
    };

    // ---- LOAD-BEARING: the exact bytes SqlDriverForm emits must construct a driver through the factory ----

    [Theory]
    // Minimal valid config: provider + required connectionStringRef only (all timeouts left to the factory).
    [InlineData("""{"provider":"SqlServer","connectionStringRef":"AdminUiFormTest"}""")]
    // Fuller config with every optional the form authors (timeouts satisfy operationTimeout > commandTimeout).
    [InlineData("""{"provider":"SqlServer","connectionStringRef":"AdminUiFormTest","defaultPollInterval":"00:00:05","operationTimeout":"00:00:20","commandTimeout":"00:00:10","maxConcurrentGroups":8,"nullIsBad":true}""")]
    public void FormEmittedJson_constructs_a_driver_through_the_factory(string formEmittedJson)
    {
        var envVar = SqlConnectionStringResolver.EnvironmentVariableFor("AdminUiFormTest");
        var previous = Environment.GetEnvironmentVariable(envVar);
        Environment.SetEnvironmentVariable(envVar, "Server=localhost;Database=Test;Trusted_Connection=True;");
        try
        {
            var driver = SqlDriverFactoryExtensions.CreateInstance("adminui-form-test", formEmittedJson);
            driver.ShouldNotBeNull();
        }
        finally
        {
            Environment.SetEnvironmentVariable(envVar, previous);
        }
    }

    [Fact]
    public void Factory_rejects_a_blob_missing_connectionStringRef()
    {
        // The form marks connectionStringRef required; the factory is the authoritative enforcement.
        var ex = Should.Throw<InvalidOperationException>(
            () => SqlDriverFactoryExtensions.CreateInstance("adminui-form-test", """{"provider":"SqlServer"}"""));
        ex.Message.ShouldContain("connectionStringRef");
    }

    // ---- serialization contract: enum-by-name, camelCase, no rawTags / allowWrites / null optionals --------

    [Fact]
    public void Minimal_config_serializes_provider_as_a_name_and_omits_owned_and_null_fields()
    {
        // Mirrors SqlDriverForm.FormModel.ToDto() for a minimal config (only the required ref set).
        var dto = new SqlDriverConfigDto { Provider = SqlProvider.SqlServer, ConnectionStringRef = "MesStaging" };

        var json = JsonSerializer.Serialize(dto, FormOptions);

        json.ShouldBe("""{"provider":"SqlServer","connectionStringRef":"MesStaging"}""");
        json.ShouldNotContain("\"provider\":0");   // never an ordinal — that faults the string-typed factory read
        json.ShouldNotContain("rawTags");           // composer-owned; never authored by the form
        json.ShouldNotContain("allowWrites");       // inert in v1 (read-only); never authored
        json.ShouldNotContain("defaultPollInterval"); // null optional omitted ⇒ factory default applies
    }

    [Fact]
    public void Provider_round_trips_as_the_SqlServer_name()
    {
        var json = JsonSerializer.Serialize(
            new SqlDriverConfigDto { Provider = SqlProvider.SqlServer, ConnectionStringRef = "R" }, FormOptions);

        json.ShouldContain("\"SqlServer\"");
        JsonSerializer.Deserialize<SqlDriverConfigDto>(json, FormOptions)!.Provider.ShouldBe(SqlProvider.SqlServer);
    }
}
