using System.Text.Json;
using ScadaLink.Commons.Messages.Artifacts;
using ScadaLink.Commons.Messages.Communication;
using ScadaLink.Commons.Messages.Deployment;
using ScadaLink.Commons.Messages.Health;
using ScadaLink.Commons.Messages.Lifecycle;
using ScadaLink.Commons.Messages.ScriptExecution;
using ScadaLink.Commons.Messages.Streaming;
using ScadaLink.Commons.Types.Enums;

namespace ScadaLink.Commons.Tests.Messages;

/// <summary>
/// WP-9 (Phase 8): Message contract compatibility tests.
/// Verifies forward compatibility (unknown fields), backward compatibility (missing optional fields),
/// and version skew scenarios for all critical message types.
/// </summary>
public class CompatibilityTests
{
    private static readonly JsonSerializerOptions Options = new()
    {
        PropertyNameCaseInsensitive = true
    };

    // ── Forward Compatibility: unknown fields are ignored ──

    [Fact]
    public void ForwardCompat_DeployInstanceCommand_UnknownFieldIgnored()
    {
        var json = """
            {
                "DeploymentId": "dep-1",
                "InstanceUniqueName": "inst-1",
                "RevisionHash": "abc123",
                "FlattenedConfigurationJson": "{}",
                "DeployedBy": "admin",
                "Timestamp": "2025-01-01T00:00:00+00:00",
                "FutureField": "unknown-value",
                "AnotherNewField": 42
            }
            """;

        var msg = JsonSerializer.Deserialize<DeployInstanceCommand>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal("dep-1", msg!.DeploymentId);
        Assert.Equal("abc123", msg.RevisionHash);
    }

    [Fact]
    public void ForwardCompat_SiteHealthReport_UnknownFieldIgnored()
    {
        var json = """
            {
                "SiteId": "site-01",
                "SequenceNumber": 5,
                "ReportTimestamp": "2025-01-01T00:00:00+00:00",
                "DataConnectionStatuses": {},
                "TagResolutionCounts": {},
                "ScriptErrorCount": 0,
                "AlarmEvaluationErrorCount": 0,
                "StoreAndForwardBufferDepths": {},
                "DeadLetterCount": 0,
                "FutureMetric": 99
            }
            """;

        var msg = JsonSerializer.Deserialize<SiteHealthReport>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal("site-01", msg!.SiteId);
        Assert.Equal(5, msg.SequenceNumber);
    }

    [Fact]
    public void ForwardCompat_ScriptCallRequest_UnknownFieldIgnored()
    {
        var json = """
            {
                "CorrelationId": "corr-1",
                "InstanceUniqueName": "inst-1",
                "ScriptName": "OnTrigger",
                "Parameters": {},
                "Timestamp": "2025-01-01T00:00:00+00:00",
                "NewExecutionMode": "parallel"
            }
            """;

        var msg = JsonSerializer.Deserialize<ScriptCallRequest>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal("corr-1", msg!.CorrelationId);
        Assert.Equal("OnTrigger", msg.ScriptName);
    }

    [Fact]
    public void ForwardCompat_AttributeValueChanged_UnknownFieldIgnored()
    {
        var json = """
            {
                "InstanceUniqueName": "inst-1",
                "AttributeName": "Temperature",
                "TagPath": "opc:ns=2;s=Temp",
                "Value": 42.5,
                "Quality": "Good",
                "Timestamp": "2025-01-01T00:00:00+00:00",
                "SourceInfo": {"origin": "future-feature"}
            }
            """;

        var msg = JsonSerializer.Deserialize<AttributeValueChanged>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal("Temperature", msg!.AttributeName);
    }

    // ── Backward Compatibility: missing optional fields ──

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

        var msg = JsonSerializer.Deserialize<DeploymentStatusResponse>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal("dep-1", msg!.DeploymentId);
        Assert.Null(msg.ErrorMessage);
    }

    [Fact]
    public void BackwardCompat_ScriptCallResult_MissingReturnValue()
    {
        var json = """
            {
                "CorrelationId": "corr-1",
                "Success": false,
                "ErrorMessage": "Script not found"
            }
            """;

        var msg = JsonSerializer.Deserialize<ScriptCallResult>(json, Options);
        Assert.NotNull(msg);
        Assert.False(msg!.Success);
        Assert.Null(msg.ReturnValue);
    }

    [Fact]
    public void BackwardCompat_DeployArtifactsCommand_MissingOptionalLists()
    {
        var json = """
            {
                "DeploymentId": "dep-1",
                "Timestamp": "2025-01-01T00:00:00+00:00"
            }
            """;

        var msg = JsonSerializer.Deserialize<DeployArtifactsCommand>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal("dep-1", msg!.DeploymentId);
        Assert.Null(msg.SharedScripts);
        Assert.Null(msg.ExternalSystems);
    }

    [Fact]
    public void BackwardCompat_InstanceLifecycleResponse_MissingErrorMessage()
    {
        var json = """
            {
                "CommandId": "cmd-1",
                "InstanceUniqueName": "inst-1",
                "Success": true,
                "Timestamp": "2025-01-01T00:00:00+00:00"
            }
            """;

        var msg = JsonSerializer.Deserialize<InstanceLifecycleResponse>(json, Options);
        Assert.NotNull(msg);
        Assert.True(msg!.Success);
        Assert.Null(msg.ErrorMessage);
    }

    // ── Version Skew: old message format still deserializable ──

    [Fact]
    public void VersionSkew_OldDeployCommand_DeserializesWithDefaults()
    {
        // Simulate an older version that only had DeploymentId and InstanceUniqueName
        var json = """
            {
                "DeploymentId": "dep-old",
                "InstanceUniqueName": "inst-old",
                "RevisionHash": "old-hash",
                "FlattenedConfigurationJson": "{}",
                "DeployedBy": "admin",
                "Timestamp": "2024-06-01T00:00:00+00:00"
            }
            """;

        var msg = JsonSerializer.Deserialize<DeployInstanceCommand>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal("dep-old", msg!.DeploymentId);
        Assert.Equal("old-hash", msg.RevisionHash);
    }

    [Fact]
    public void VersionSkew_OldHealthReport_DeserializesCorrectly()
    {
        // Older version without DeadLetterCount
        var json = """
            {
                "SiteId": "site-old",
                "SequenceNumber": 1,
                "ReportTimestamp": "2024-06-01T00:00:00+00:00",
                "DataConnectionStatuses": {"conn1": 0},
                "TagResolutionCounts": {},
                "ScriptErrorCount": 0,
                "AlarmEvaluationErrorCount": 0,
                "StoreAndForwardBufferDepths": {}
            }
            """;

        var msg = JsonSerializer.Deserialize<SiteHealthReport>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal("site-old", msg!.SiteId);
        Assert.Equal(0, msg.DeadLetterCount); // Default value
    }

    // ── Round-trip serialization for all key message types ──

    [Fact]
    public void RoundTrip_ConnectionStateChanged_Succeeds()
    {
        var msg = new ConnectionStateChanged("site-01", true, DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<ConnectionStateChanged>(json, Options);

        Assert.NotNull(deserialized);
        Assert.Equal("site-01", deserialized!.SiteId);
        Assert.True(deserialized.IsConnected);
    }

    [Fact]
    public void RoundTrip_AlarmStateChanged_Succeeds()
    {
        var msg = new AlarmStateChanged("inst-1", "HighTemp", AlarmState.Active, 1, DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<AlarmStateChanged>(json, Options);

        Assert.NotNull(deserialized);
        Assert.Equal(AlarmState.Active, deserialized!.State);
        Assert.Equal("HighTemp", deserialized.AlarmName);
    }

    [Fact]
    public void RoundTrip_HeartbeatMessage_Succeeds()
    {
        var msg = new HeartbeatMessage("site-01", "node-a", true, DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<HeartbeatMessage>(json, Options);

        Assert.NotNull(deserialized);
        Assert.Equal("site-01", deserialized!.SiteId);
        Assert.Equal("node-a", deserialized.NodeHostname);
    }

    [Fact]
    public void RoundTrip_DisableInstanceCommand_Succeeds()
    {
        var msg = new DisableInstanceCommand("cmd-1", "inst-1", DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<DisableInstanceCommand>(json, Options);

        Assert.NotNull(deserialized);
        Assert.Equal("cmd-1", deserialized!.CommandId);
    }

    [Fact]
    public void RoundTrip_EnableInstanceCommand_Succeeds()
    {
        var msg = new EnableInstanceCommand("cmd-2", "inst-1", DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<EnableInstanceCommand>(json, Options);

        Assert.NotNull(deserialized);
        Assert.Equal("cmd-2", deserialized!.CommandId);
    }

    [Fact]
    public void RoundTrip_DeleteInstanceCommand_Succeeds()
    {
        var msg = new DeleteInstanceCommand("cmd-3", "inst-1", DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<DeleteInstanceCommand>(json, Options);

        Assert.NotNull(deserialized);
        Assert.Equal("cmd-3", deserialized!.CommandId);
    }

    // ── Additive-only evolution: new fields added as nullable ──

    [Fact]
    public void AdditiveEvolution_NewNullableFields_DoNotBreakDeserialization()
    {
        // The design mandates additive-only evolution for message contracts.
        // New fields must be nullable/optional so old producers don't break new consumers.
        // This test verifies the pattern works with System.Text.Json.

        var minimalJson = """{"DeploymentId":"dep-1","InstanceUniqueName":"inst-1","Status":1,"Timestamp":"2025-01-01T00:00:00+00:00"}""";

        var msg = JsonSerializer.Deserialize<DeploymentStatusResponse>(minimalJson, Options);
        Assert.NotNull(msg);
        Assert.Null(msg!.ErrorMessage); // Optional field defaults to null
    }

    [Fact]
    public void EnumDeserialization_UnknownValue_HandledGracefully()
    {
        // If a newer version adds a new enum value, older consumers should handle it.
        // System.Text.Json will deserialize unknown numeric enum values as the numeric value.
        var json = """{"DeploymentId":"dep-1","InstanceUniqueName":"inst-1","Status":99,"Timestamp":"2025-01-01T00:00:00+00:00"}""";

        var msg = JsonSerializer.Deserialize<DeploymentStatusResponse>(json, Options);
        Assert.NotNull(msg);
        Assert.Equal((DeploymentStatus)99, msg!.Status);
    }

    // ── DeploymentManager-006: query-the-site-before-redeploy contracts ──

    [Fact]
    public void RoundTrip_DeploymentStateQueryRequest_Succeeds()
    {
        var msg = new DeploymentStateQueryRequest("corr-1", "inst-1", DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<DeploymentStateQueryRequest>(json, Options);

        Assert.NotNull(deserialized);
        Assert.Equal("corr-1", deserialized!.CorrelationId);
        Assert.Equal("inst-1", deserialized.InstanceUniqueName);
    }

    [Fact]
    public void RoundTrip_DeploymentStateQueryResponse_Deployed_Succeeds()
    {
        var msg = new DeploymentStateQueryResponse(
            "corr-1", "inst-1", true, "dep-9", "sha256:abc", DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<DeploymentStateQueryResponse>(json, Options);

        Assert.NotNull(deserialized);
        Assert.True(deserialized!.IsDeployed);
        Assert.Equal("dep-9", deserialized.AppliedDeploymentId);
        Assert.Equal("sha256:abc", deserialized.AppliedRevisionHash);
    }

    [Fact]
    public void RoundTrip_DeploymentStateQueryResponse_NotDeployed_NullApplied()
    {
        // When the instance is not deployed at the site, the applied identity
        // fields are null — verified to survive a JSON round-trip.
        var msg = new DeploymentStateQueryResponse(
            "corr-1", "inst-1", false, null, null, DateTimeOffset.UtcNow);
        var json = JsonSerializer.Serialize(msg);
        var deserialized = JsonSerializer.Deserialize<DeploymentStateQueryResponse>(json, Options);

        Assert.NotNull(deserialized);
        Assert.False(deserialized!.IsDeployed);
        Assert.Null(deserialized.AppliedDeploymentId);
        Assert.Null(deserialized.AppliedRevisionHash);
    }
}
