using System.Buffers.Binary;
using Flyshot.Runtime.Fanuc.Protocol;

namespace Flyshot.Core.Tests;

/// <summary>
/// 验证 FANUC 真机三条通信链路的二进制协议基础与逆向抓包样本一致。
/// </summary>
public sealed class FanucProtocolTests
{
    /// <summary>
    /// 验证 TCP 10012 程序命令封包与抓包中的 StopProg("RVBUSTSM") 完全一致。
    /// </summary>
    [Fact]
    public void CommandProtocol_PacksCapturedStopProgramFrame()
    {
        var frame = FanucCommandProtocol.PackProgramCommand(FanucCommandMessageIds.StopProgram, "RVBUSTSM");

        Assert.Equal(
            Convert.FromHexString("646f7a0000001a0000210300000008525642555354534d7a6f64"),
            frame);
    }

    /// <summary>
    /// 验证 TCP 10012 短响应和程序状态响应可以按抓包字段解析。
    /// </summary>
    [Fact]
    public void CommandProtocol_ParsesCapturedResponses()
    {
        var stopResponse = FanucCommandProtocol.ParseResultResponse(
            Convert.FromHexString("646f7a0000001200002103000000007a6f64"));
        var statusResponse = FanucCommandProtocol.ParseProgramStatusResponse(
            Convert.FromHexString("646f7a000000160000200300000001000000007a6f64"));

        Assert.Equal(FanucCommandMessageIds.StopProgram, stopResponse.MessageId);
        Assert.True(stopResponse.IsSuccess);
        Assert.Equal(FanucCommandMessageIds.GetProgramStatus, statusResponse.MessageId);
        Assert.True(statusResponse.IsSuccess);
        Assert.Equal(1u, statusResponse.ProgramStatus);
    }

    /// <summary>
    /// 验证 TCP 10012 的速度倍率、TCP 和 IO 请求体字段顺序与逆向文档一致。
    /// </summary>
    [Fact]
    public void CommandProtocol_PacksParameterCommandBodies()
    {
        var setTcpFrame = FanucCommandProtocol.PackSetTcpCommand(1, [1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0]);

        Assert.Equal(
            Convert.FromHexString("646f7a0000000e000022067a6f64"),
            FanucCommandProtocol.PackGetSpeedRatioCommand());
        Assert.Equal(
            Convert.FromHexString("646f7a0000001200002207000000507a6f64"),
            FanucCommandProtocol.PackSetSpeedRatioCommand(0.8));
        Assert.Equal(
            Convert.FromHexString("646f7a0000001200002200000000017a6f64"),
            FanucCommandProtocol.PackGetTcpCommand(1));
        Assert.Equal(
            Convert.FromHexString("646f7a000000160000220800000002000000077a6f64"),
            FanucCommandProtocol.PackGetIoCommand(FanucIoTypes.DigitalOutput, 7));
        Assert.Equal(
            Convert.FromHexString("646f7a0000001a0000220900000002000000073f8000007a6f64"),
            FanucCommandProtocol.PackSetIoCommand(FanucIoTypes.DigitalOutput, 7, true));
        Assert.Equal(FanucCommandMessageIds.SetTcp, BinaryPrimitives.ReadUInt32BigEndian(setTcpFrame.AsSpan(7, 4)));
        Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(setTcpFrame.AsSpan(11, 4)));
        Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(setTcpFrame.AsSpan(15, 4)));
        Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(setTcpFrame.AsSpan(39, 4)));
    }

    /// <summary>
    /// 验证 TCP 10012 参数响应解析使用各自不同的字段顺序。
    /// </summary>
    [Fact]
    public void CommandProtocol_ParsesParameterResponses()
    {
        var speedRatioResponse = FanucCommandProtocol.ParseSpeedRatioResponse(
            FanucCommandProtocol.PackFrame(
                FanucCommandMessageIds.GetSpeedRatio,
                Convert.FromHexString("0000005000000000")));
        var tcpResponse = FanucCommandProtocol.ParseTcpResponse(
            FanucCommandProtocol.PackFrame(
                FanucCommandMessageIds.GetTcp,
                Convert.FromHexString("00000000000000013f80000040000000404000000000000000000000000000003f800000")));
        var ioResponse = FanucCommandProtocol.ParseIoResponse(
            FanucCommandProtocol.PackFrame(
                FanucCommandMessageIds.GetIo,
                Convert.FromHexString("000000003f800000")));

        Assert.True(speedRatioResponse.IsSuccess);
        Assert.Equal(0.8, speedRatioResponse.Ratio, precision: 6);
        Assert.True(tcpResponse.IsSuccess);
        Assert.Equal(1u, tcpResponse.TcpId);
        Assert.Equal([1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0], tcpResponse.Pose);
        Assert.True(ioResponse.IsSuccess);
        Assert.True(ioResponse.Value);
        Assert.Equal(1.0, ioResponse.NumericValue, precision: 6);
    }

    /// <summary>
    /// 验证 TCP 10010 状态帧可以从抓包样本解析出尾部状态槽位。
    /// </summary>
    [Fact]
    public void StateProtocol_ParsesCapturedStateFrame()
    {
        var frame = FanucStateProtocol.ParseFrame(Convert.FromHexString(
            "646f7a0000005a000000004388a23243f1ed7f43e9de6bc265031ec2b33cc3c278e0153f8742f53c3f128dbc929529bc7861d63cb0184c3c1ca1a7000000000000000000000000000000020000000000000000000000017a6f64"));

        Assert.Equal(0u, frame.MessageId);
        Assert.Equal(6, frame.Pose.Count);
        Assert.Equal(9, frame.JointOrExtensionValues.Count);
        Assert.Equal([2u, 0u, 0u, 1u], frame.TailWords);
        Assert.Equal(frame.Pose, frame.CartesianPose);
        Assert.Equal(frame.JointOrExtensionValues.Take(6), frame.JointRadians);
        Assert.Equal(frame.JointOrExtensionValues.Skip(6), frame.ExternalAxes);
        Assert.Equal(frame.TailWords, frame.RawTailWords);
        Assert.Equal(2u, frame.StatusWord0);
        Assert.Equal(0u, frame.StatusWord1);
        Assert.Equal(0u, frame.StatusWord2);
        Assert.Equal(1u, frame.StatusWord3);
    }

    /// <summary>
    /// 验证 pcap 中多条唯一 TCP 10010 状态帧都符合固定 90B 布局。
    /// </summary>
    [Theory]
    [InlineData("646f7a0000005a0000000040eac85a43b2ef4043aba8e9421ed9c1c2828105c2ed981f3fbdbda0bed4764ebe92aacc3efd9f0a3f317ce9be5d4580000000000000000000000000000000020000000000000000000000017a6f64")]
    [InlineData("646f7a0000005a00000000415aab64440a5302439adef542b39739c293c441431d50423fcdb7003d862fe3beca5730bf60eab23f148e403f89269d000000000000000000000000000000020000000000000000000000017a6f64")]
    [InlineData("646f7a0000005a000000004221b6f9440b9ce043a129ac42b292bac29cba78431bddcb3fc743213d90268dbeba5351bf64bc1b3f0cbdf73f826864000000000000000000000000000000020000000000000000000000017a6f64")]
    public void StateProtocol_ParsesMultipleCapturedPcapFrames(string frameHex)
    {
        var frameBytes = Convert.FromHexString(frameHex);

        var frame = FanucStateProtocol.ParseFrame(frameBytes);

        Assert.Equal(FanucStateProtocol.StateFrameLength, frameBytes.Length);
        Assert.Equal(6, frame.CartesianPose.Count);
        Assert.Equal(6, frame.JointRadians.Count);
        Assert.Equal(3, frame.ExternalAxes.Count);
        Assert.Equal([2u, 0u, 0u, 1u], frame.RawTailWords);
    }

    /// <summary>
    /// 验证 TCP 10010 状态帧会拒绝损坏的长度和 magic，避免后台循环缓存坏帧。
    /// </summary>
    [Fact]
    public void StateProtocol_RejectsMalformedStateFrames()
    {
        var validFrame = Convert.FromHexString(
            "646f7a0000005a000000004388a23243f1ed7f43e9de6bc265031ec2b33cc3c278e0153f8742f53c3f128dbc929529bc7861d63cb0184c3c1ca1a7000000000000000000000000000000020000000000000000000000017a6f64");
        var wrongMagic = validFrame.ToArray();
        wrongMagic[0] = 0;
        var wrongLength = validFrame.ToArray();
        wrongLength[6] = 0x59;

        Assert.Throws<InvalidDataException>(() => FanucStateProtocol.ParseFrame(validFrame.AsSpan(0, validFrame.Length - 1)));
        Assert.Throws<InvalidDataException>(() => FanucStateProtocol.ParseFrame(wrongMagic));
        Assert.Throws<InvalidDataException>(() => FanucStateProtocol.ParseFrame(wrongLength));
    }

    /// <summary>
    /// 验证 UDP 60015 的 J519 初始化、结束和命令包字段布局。
    /// </summary>
    [Fact]
    public void J519Protocol_PacksControlAndCommandPackets()
    {
        var command = new FanucJ519Command(
            sequence: 2,
            targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);

        var packet = FanucJ519Protocol.PackCommandPacket(command);

        Assert.Equal(Convert.FromHexString("0000000000000001"), FanucJ519Protocol.PackInitPacket());
        Assert.Equal(Convert.FromHexString("0000000200000001"), FanucJ519Protocol.PackEndPacket());
        Assert.Equal(FanucJ519Protocol.CommandPacketLength, packet.Length);
        Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x00, 4)));
        Assert.Equal(1u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x04, 4)));
        Assert.Equal(2u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x08, 4)));
        Assert.Equal(2, packet[0x0d]);
        Assert.Equal(1, packet[0x12]);
        Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x1c, 4)));
        Assert.Equal(6.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x30, 4)));
        Assert.Equal(0.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x38, 4)));
    }

    /// <summary>
    /// 验证实时发送路径可以复用命令对象，并用机器人状态包序号覆盖出包序号。
    /// </summary>
    [Fact]
    public void J519Protocol_PacksCommandWithOverrideSequenceIntoReusableBuffer()
    {
        var command = new FanucJ519Command(
            sequence: 2,
            targetJoints: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
        var packet = new byte[FanucJ519Protocol.CommandPacketLength];

        FanucJ519Protocol.PackCommandPacket(command, sequence: 456, packet);

        Assert.Equal(456u, BinaryPrimitives.ReadUInt32BigEndian(packet.AsSpan(0x08, 4)));
        Assert.Equal(1.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x1c, 4)));
        Assert.Equal(6.0f, BinaryPrimitives.ReadSingleBigEndian(packet.AsSpan(0x30, 4)));
    }

    /// <summary>
    /// 验证 UDP 60015 的 132 字节响应包字段可以被解析成状态位和关节反馈。
    /// </summary>
    [Fact]
    public void J519Protocol_ParsesResponsePacket()
    {
        var packet = new byte[FanucJ519Protocol.ResponsePacketLength];
        BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x00, 4), 0);
        BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x04, 4), 1);
        BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x08, 4), 12);
        packet[0x0c] = 15;
        packet[0x0d] = 2;
        BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x0e, 2), 1);
        BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x10, 2), 255);
        BinaryPrimitives.WriteUInt16BigEndian(packet.AsSpan(0x12, 2), 10);
        BinaryPrimitives.WriteUInt32BigEndian(packet.AsSpan(0x14, 4), 1234);
        BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x18, 4), 100.5f);
        BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x3c, 4), 1.25f);
        BinaryPrimitives.WriteSingleBigEndian(packet.AsSpan(0x60, 4), 2.5f);

        var response = FanucJ519Protocol.ParseResponse(packet);

        Assert.Equal(12u, response.Sequence);
        Assert.Equal(15, response.Status);
        Assert.True(response.AcceptsCommand);
        Assert.True(response.ReceivedCommand);
        Assert.True(response.SystemReady);
        Assert.True(response.RobotInMotion);
        Assert.Equal(10, response.ReadIoValue);
        Assert.Equal(1234u, response.Timestamp);
        Assert.Equal(100.5, response.Pose[0], precision: 6);
        Assert.Equal(1.25, response.JointDegrees[0], precision: 6);
        Assert.Equal(2.5, response.MotorCurrents[0], precision: 6);
    }
}
