using System.Reflection;
using System.Text.Json;

namespace ZB.MOM.WW.ScadaBridge.Commons.Tests.Messages;

public class MessageConventionTests
{
    private static readonly Assembly CommonsAssembly = typeof(ZB.MOM.WW.ScadaBridge.Commons.Types.RetryPolicy).Assembly;

    private static IEnumerable<Type> GetMessageTypes() =>
        CommonsAssembly.GetTypes()
            .Where(t => t.Namespace != null
                        && t.Namespace.Contains(".Messages.")
                        && !t.IsEnum
                        && !t.IsInterface
                        && !(t.IsAbstract && t.IsSealed) // exclude static classes (utilities)
                        && !t.Name.StartsWith("<") // exclude compiler-generated types
                        && (t.IsClass || (t.IsValueType && !t.IsPrimitive)));

    [Fact]
    public void AllMessageTypes_ShouldBeRecords()
    {
        foreach (var type in GetMessageTypes())
        {
            // Records have a compiler-generated <Clone>$ method
            var cloneMethod = type.GetMethod("<Clone>$", BindingFlags.Public | BindingFlags.Instance);
            Assert.True(cloneMethod != null,
                $"{type.FullName} in Messages namespace should be a record type");
        }
    }

    [Fact]
    public void AllMessageTimestampProperties_ShouldBeDateTimeOffset()
    {
        foreach (var type in GetMessageTypes())
        {
            foreach (var prop in type.GetProperties())
            {
                if (prop.Name.Contains("Timestamp") || prop.Name == "GeneratedAt" || prop.Name == "DeployedAt")
                {
                    var underlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
                    Assert.True(underlyingType == typeof(DateTimeOffset),
                        $"{type.Name}.{prop.Name} should be DateTimeOffset but is {prop.PropertyType.Name}");
                }
            }
        }
    }

    [Fact]
    public void JsonRoundTrip_DeployInstanceCommand_ShouldSucceed()
    {
        var msg = new ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment.DeployInstanceCommand(
            "dep-1", "instance-1", "abc123", "{}", "admin", DateTimeOffset.UtcNow);

        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment.DeployInstanceCommand>(json);

        Assert.NotNull(deserialized);
        Assert.Equal(msg.DeploymentId, deserialized!.DeploymentId);
        Assert.Equal(msg.InstanceUniqueName, deserialized.InstanceUniqueName);
    }

    [Fact]
    public void JsonForwardCompatibility_UnknownField_ShouldDeserialize()
    {
        // Simulate a newer version with an extra field
        var json = """{"DeploymentId":"dep-1","InstanceUniqueName":"inst-1","RevisionHash":"abc","FlattenedConfigurationJson":"{}","DeployedBy":"admin","Timestamp":"2025-01-01T00:00:00+00:00","NewField":"extra"}""";

        var deserialized = JsonSerializer.Deserialize<ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment.DeployInstanceCommand>(json);

        Assert.NotNull(deserialized);
        Assert.Equal("dep-1", deserialized!.DeploymentId);
    }

    [Fact]
    public void JsonBackwardCompatibility_MissingOptionalField_ShouldDeserialize()
    {
        // DeploymentStatusResponse has nullable ErrorMessage
        var json = """{"DeploymentId":"dep-1","InstanceUniqueName":"inst-1","Status":2,"Timestamp":"2025-01-01T00:00:00+00:00"}""";

        var deserialized = JsonSerializer.Deserialize<ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment.DeploymentStatusResponse>(json);

        Assert.NotNull(deserialized);
        Assert.Equal("dep-1", deserialized!.DeploymentId);
        Assert.Null(deserialized.ErrorMessage);
    }

    [Fact]
    public void JsonRoundTrip_SiteHealthReport_ShouldSucceed()
    {
        var msg = new ZB.MOM.WW.ScadaBridge.Commons.Messages.Health.SiteHealthReport(
            "site-1", 1, DateTimeOffset.UtcNow,
            new Dictionary<string, ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.ConnectionHealth>
            {
                ["conn1"] = ZB.MOM.WW.ScadaBridge.Commons.Types.Enums.ConnectionHealth.Connected
            },
            new Dictionary<string, ZB.MOM.WW.ScadaBridge.Commons.Messages.Health.TagResolutionStatus>
            {
                ["conn1"] = new(10, 8)
            },
            0, 0,
            new Dictionary<string, int> { ["queue1"] = 5 },
            0, 0, 0, 0);

        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<ZB.MOM.WW.ScadaBridge.Commons.Messages.Health.SiteHealthReport>(json);

        Assert.NotNull(deserialized);
        Assert.Equal("site-1", deserialized!.SiteId);
        Assert.Equal(1, deserialized.SequenceNumber);
    }

    [Fact]
    public void JsonRoundTrip_DeployArtifactsCommand_ShouldSucceed()
    {
        var msg = new ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts.DeployArtifactsCommand(
            "dep-1",
            new List<ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts.SharedScriptArtifact>
            {
                new("script1", "code", null, null)
            },
            null, null, null, null, null,
            DateTimeOffset.UtcNow);

        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts.DeployArtifactsCommand>(json);

        Assert.NotNull(deserialized);
        Assert.Equal("dep-1", deserialized!.DeploymentId);
        Assert.Single(deserialized.SharedScripts!);
    }
}
