using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Contracts;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Shared.Tests;

[Trait("Category", "Unit")]
public sealed class FramingTests
{
    [Fact]
    public async Task FrameWriter_FrameReader_round_trip_preserves_kind_and_body()
    {
        using var ms = new MemoryStream();

        using (var writer = new FrameWriter(ms, leaveOpen: true))
        {
            await writer.WriteAsync(MessageKind.Hello,
                new Hello { PeerName = "p", SharedSecret = "s" }, TestContext.Current.CancellationToken);
            await writer.WriteAsync(MessageKind.Heartbeat,
                new Heartbeat { SequenceNumber = 7, UtcUnixMs = 42 }, TestContext.Current.CancellationToken);
        }

        ms.Position = 0;
        using var reader = new FrameReader(ms, leaveOpen: true);

        var f1 = (await reader.ReadFrameAsync(TestContext.Current.CancellationToken))!.Value;
        f1.Kind.ShouldBe(MessageKind.Hello);
        FrameReader.Deserialize<Hello>(f1.Body).PeerName.ShouldBe("p");

        var f2 = (await reader.ReadFrameAsync(TestContext.Current.CancellationToken))!.Value;
        f2.Kind.ShouldBe(MessageKind.Heartbeat);
        FrameReader.Deserialize<Heartbeat>(f2.Body).SequenceNumber.ShouldBe(7L);

        var eof = await reader.ReadFrameAsync(TestContext.Current.CancellationToken);
        eof.ShouldBeNull();
    }

    [Fact]
    public async Task FrameReader_rejects_frames_larger_than_the_cap()
    {
        using var ms = new MemoryStream();
        var evilLen = Framing.MaxFrameBodyBytes + 1;
        ms.Write(new byte[]
        {
            (byte)((evilLen >> 24) & 0xFF),
            (byte)((evilLen >> 16) & 0xFF),
            (byte)((evilLen >> 8)  & 0xFF),
            (byte)( evilLen        & 0xFF),
        }, 0, 4);
        ms.WriteByte((byte)MessageKind.Hello);
        ms.Position = 0;

        using var reader = new FrameReader(ms, leaveOpen: true);
        await Should.ThrowAsync<InvalidDataException>(() =>
            reader.ReadFrameAsync(TestContext.Current.CancellationToken).AsTask());
    }

    private static class TestContext
    {
        public static TestContextHelper Current { get; } = new();
    }

    private sealed class TestContextHelper
    {
        public CancellationToken CancellationToken => CancellationToken.None;
    }
}

file static class TaskExtensions
{
    public static Task AsTask<T>(this ValueTask<T> vt) => vt.AsTask();
    public static Task AsTask<T>(this Task<T> t) => t;
}
