using RioJoy.Core.Protocol;
using Xunit;

namespace RioJoy.Core.Tests.Protocol;

public class PacketParserTests
{
    private static List<RioRxEvent> FeedAll(PacketParser parser, byte[] data)
    {
        var events = new List<RioRxEvent>();
        foreach (byte b in data)
        {
            if (parser.Feed(b, out RioRxEvent ev))
                events.Add(ev);
        }

        return events;
    }

    [Fact]
    public void FramesValidPacket_WithChecksumValid()
    {
        var parser = new PacketParser();
        byte[] frame = PacketBuilder.Build(RioCommand.ButtonPressed, new byte[] { 0x05 });

        List<RioRxEvent> events = FeedAll(parser, frame);

        RioRxEvent ev = Assert.Single(events);
        Assert.Equal(RioRxEventKind.Packet, ev.Kind);
        Assert.True(ev.ChecksumValid);
        Assert.Equal(RioCommand.ButtonPressed, ev.Packet.Command);
        Assert.Equal(new byte[] { 0x05 }, ev.Packet.Payload.ToArray());
        Assert.False(parser.InPacket);
    }

    [Fact]
    public void ReportsChecksumInvalid_ButStillFramesPacket()
    {
        var parser = new PacketParser();
        byte[] frame = PacketBuilder.Build(RioCommand.ButtonPressed, new byte[] { 0x05 });
        frame[^1] ^= 0x01; // corrupt checksum (stays high-bit clear)

        RioRxEvent ev = Assert.Single(FeedAll(parser, frame));
        Assert.Equal(RioRxEventKind.Packet, ev.Kind);
        Assert.False(ev.ChecksumValid);
    }

    [Fact]
    public void ControlByte_OutsideFraming_IsReported()
    {
        var parser = new PacketParser();

        RioRxEvent ev = Assert.Single(FeedAll(parser, new[] { (byte)RioControl.Ack }));
        Assert.Equal(RioRxEventKind.ControlByte, ev.Kind);
        Assert.True(ev.IsControl(RioControl.Ack));
    }

    [Fact]
    public void HighBitMidPacket_AbortsAndResyncs()
    {
        var parser = new PacketParser();

        // Start a ButtonPressed (expects 1 payload + 1 checksum), feed one payload
        // byte, then a high-bit byte mid-packet → framing error + resync.
        var events = new List<RioRxEvent>();
        foreach (byte b in new byte[] { 0x88, 0x05, 0x82 })
        {
            if (parser.Feed(b, out RioRxEvent ev))
                events.Add(ev);
        }

        RioRxEvent framing = Assert.Single(events);
        Assert.Equal(RioRxEventKind.FramingError, framing.Kind);
        Assert.False(parser.InPacket);

        // A subsequent clean frame parses normally (the parser resynced).
        byte[] next = PacketBuilder.Build(RioCommand.ButtonReleased, new byte[] { 0x07 });
        RioRxEvent ev2 = Assert.Single(FeedAll(parser, next));
        Assert.Equal(RioCommand.ButtonReleased, ev2.Packet.Command);
    }

    [Fact]
    public void ParsesMultiplePackets_InOneStream()
    {
        var parser = new PacketParser();
        byte[] a = PacketBuilder.Build(RioCommand.ButtonPressed, new byte[] { 0x01 });
        byte[] b = PacketBuilder.Build(RioCommand.ButtonReleased, new byte[] { 0x02 });
        byte[] stream = new byte[a.Length + b.Length];
        a.CopyTo(stream, 0);
        b.CopyTo(stream, a.Length);

        List<RioRxEvent> events = FeedAll(parser, stream);

        Assert.Equal(2, events.Count);
        Assert.Equal(RioCommand.ButtonPressed, events[0].Packet.Command);
        Assert.Equal(RioCommand.ButtonReleased, events[1].Packet.Command);
    }

    [Fact]
    public void HandlesPacket_SplitAcrossFeeds()
    {
        var parser = new PacketParser();
        byte[] frame = PacketBuilder.Build(RioCommand.AnalogReply, new byte[] { 1, 0, 2, 0, 3, 0, 4, 0, 5, 0 });

        // Feed byte-by-byte: only the final byte should yield the packet event.
        var events = new List<RioRxEvent>();
        for (int i = 0; i < frame.Length; i++)
        {
            bool produced = parser.Feed(frame[i], out RioRxEvent ev);
            if (produced)
                events.Add(ev);

            Assert.Equal(i == frame.Length - 1, produced);
        }

        RioRxEvent done = Assert.Single(events);
        Assert.Equal(RioCommand.AnalogReply, done.Packet.Command);
        Assert.True(done.ChecksumValid);
    }
}
