using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;

namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Protos;

/// <summary>
/// Wire-format round-trip tests for the Audit Log (#23) M3 cached-telemetry
/// proto messages (<see cref="SiteCallOperationalDto"/>,
/// <see cref="CachedTelemetryPacket"/>, <see cref="CachedTelemetryBatch"/>).
/// Locks the additive contract the central dual-write transaction depends on.
/// </summary>
public class CachedTelemetryProtoTests
{
    private static AuditEventDto NewAuditDto(Guid? id = null) => new()
    {
        EventId = (id ?? Guid.NewGuid()).ToString(),
        OccurredAtUtc = Timestamp.FromDateTimeOffset(
            new DateTimeOffset(2026, 5, 20, 10, 15, 30, 123, TimeSpan.Zero)),
        Channel = "ApiOutbound",
        Kind = "CachedSubmit",
        Status = "Submitted",
        SourceSiteId = "site-1",
    };

    [Fact]
    public void SiteCallOperationalDto_RoundTrip_PreservesAllFields()
    {
        var createdAt = Timestamp.FromDateTimeOffset(
            new DateTimeOffset(2026, 5, 20, 10, 0, 0, TimeSpan.Zero));
        var updatedAt = Timestamp.FromDateTimeOffset(
            new DateTimeOffset(2026, 5, 20, 10, 5, 0, TimeSpan.Zero));
        var terminalAt = Timestamp.FromDateTimeOffset(
            new DateTimeOffset(2026, 5, 20, 10, 10, 0, TimeSpan.Zero));

        var original = new SiteCallOperationalDto
        {
            TrackedOperationId = Guid.NewGuid().ToString(),
            Channel = "ApiOutbound",
            Target = "ERP.GetOrder",
            SourceSite = "site-melbourne",
            SourceNode = "node-a",
            Status = "Delivered",
            RetryCount = 3,
            LastError = "transient 503",
            HttpStatus = 200,
            CreatedAtUtc = createdAt,
            UpdatedAtUtc = updatedAt,
            TerminalAtUtc = terminalAt,
        };

        var bytes = original.ToByteArray();
        var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes);

        Assert.Equal(original.TrackedOperationId, deserialized.TrackedOperationId);
        Assert.Equal(original.Channel, deserialized.Channel);
        Assert.Equal(original.Target, deserialized.Target);
        Assert.Equal(original.SourceSite, deserialized.SourceSite);
        Assert.Equal(original.SourceNode, deserialized.SourceNode);
        Assert.Equal(original.Status, deserialized.Status);
        Assert.Equal(original.RetryCount, deserialized.RetryCount);
        Assert.Equal(original.LastError, deserialized.LastError);
        Assert.Equal(original.HttpStatus, deserialized.HttpStatus);
        Assert.Equal(original.CreatedAtUtc, deserialized.CreatedAtUtc);
        Assert.Equal(original.UpdatedAtUtc, deserialized.UpdatedAtUtc);
        Assert.Equal(original.TerminalAtUtc, deserialized.TerminalAtUtc);
    }

    [Fact]
    public void SiteCallOperationalDto_TerminalAt_AbsentWhenNotTerminal()
    {
        // Lifecycle events prior to the terminal step leave TerminalAtUtc unset;
        // the well-known Timestamp wrapper is absent on the wire (null in C#).
        var dto = new SiteCallOperationalDto
        {
            TrackedOperationId = Guid.NewGuid().ToString(),
            Channel = "DbOutbound",
            Target = "warehouse.dbo.WriteOrder",
            SourceSite = "site-brisbane",
            Status = "Attempted",
            RetryCount = 1,
            CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
            UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
        };

        Assert.Null(dto.TerminalAtUtc);

        var bytes = dto.ToByteArray();
        var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes);

        Assert.Null(deserialized.TerminalAtUtc);
    }

    [Fact]
    public void SiteCallOperationalDto_NullableHttpStatus_AbsentByDefault()
    {
        // Int32Value wrapper-typed http_status — unset round-trips as null,
        // matching DB nullable column semantics for non-API cached writes.
        var dto = new SiteCallOperationalDto
        {
            TrackedOperationId = Guid.NewGuid().ToString(),
            Channel = "DbOutbound",
            Target = "warehouse.dbo.WriteOrder",
            SourceSite = "site-brisbane",
            Status = "Submitted",
            RetryCount = 0,
            CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
            UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
        };

        Assert.Null(dto.HttpStatus);

        var bytes = dto.ToByteArray();
        var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes);

        Assert.Null(deserialized.HttpStatus);
    }

    [Fact]
    public void CachedTelemetryPacket_RoundTrip_PreservesNestedEntities()
    {
        var trackedOpId = Guid.NewGuid().ToString();
        var auditDto = NewAuditDto();
        auditDto.Target = "ERP.GetOrder";
        auditDto.Status = "Attempted";

        var operationalDto = new SiteCallOperationalDto
        {
            TrackedOperationId = trackedOpId,
            Channel = "ApiOutbound",
            Target = "ERP.GetOrder",
            SourceSite = "site-1",
            Status = "Attempted",
            RetryCount = 2,
            HttpStatus = 503,
            LastError = "Service unavailable",
            CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
            UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
        };

        var original = new CachedTelemetryPacket
        {
            AuditEvent = auditDto,
            Operational = operationalDto,
        };

        var bytes = original.ToByteArray();
        var deserialized = CachedTelemetryPacket.Parser.ParseFrom(bytes);

        Assert.NotNull(deserialized.AuditEvent);
        Assert.Equal(auditDto.EventId, deserialized.AuditEvent.EventId);
        Assert.Equal(auditDto.Target, deserialized.AuditEvent.Target);
        Assert.Equal(auditDto.Status, deserialized.AuditEvent.Status);

        Assert.NotNull(deserialized.Operational);
        Assert.Equal(trackedOpId, deserialized.Operational.TrackedOperationId);
        Assert.Equal(operationalDto.Channel, deserialized.Operational.Channel);
        Assert.Equal(operationalDto.Status, deserialized.Operational.Status);
        Assert.Equal(operationalDto.RetryCount, deserialized.Operational.RetryCount);
        Assert.Equal(operationalDto.HttpStatus, deserialized.Operational.HttpStatus);
        Assert.Equal(operationalDto.LastError, deserialized.Operational.LastError);
    }

    [Fact]
    public void CachedTelemetryBatch_Empty_RoundTrip_Yields_EmptyPackets()
    {
        var original = new CachedTelemetryBatch();
        Assert.Empty(original.Packets);

        var bytes = original.ToByteArray();
        var deserialized = CachedTelemetryBatch.Parser.ParseFrom(bytes);

        Assert.Empty(deserialized.Packets);
    }
}
