#include "plugins/protocols/modbus_rtu/codec/ModbusRtuCodec.h"

#include <QtTest/QtTest>

#include <variant>

#include "message_bus/pipeline/PipelineContext.h"

using softbus::message_bus::pipeline::BusDirection;
using softbus::message_bus::pipeline::protocol::ModbusExceptionPdu;
using softbus::message_bus::pipeline::protocol::ModbusRtuPdu;
using softbus::message_bus::pipeline::protocol::ReadCoilsRequest;
using softbus::message_bus::pipeline::protocol::ReadCoilsResponse;
using softbus::message_bus::pipeline::protocol::ReadDiscreteInputsRequest;
using softbus::message_bus::pipeline::protocol::ReadDiscreteInputsResponse;
using softbus::message_bus::pipeline::protocol::ReadHoldingRegistersRequest;
using softbus::message_bus::pipeline::protocol::ReadHoldingRegistersResponse;
using softbus::message_bus::pipeline::protocol::decodeModbusRtu;
using softbus::message_bus::pipeline::protocol::encodeModbusRtu;

class ModbusRtuPduCodecTest : public QObject
{
    Q_OBJECT

private slots:
    void roundTrip_readCoilsRequest();
    void roundTrip_readCoilsResponse();
    void roundTrip_readDiscreteInputsRequest();
    void roundTrip_readDiscreteInputsResponse();
    void roundTrip_readHoldingRegistersRequest();
    void roundTrip_readHoldingRegistersResponse();
    void roundTrip_exception();
    void unknownDirection_heuristic_request_vs_response();
};

void ModbusRtuPduCodecTest::roundTrip_readHoldingRegistersRequest()
{
    const ReadHoldingRegistersRequest req{0x01, 40001, 4};
    QByteArray                        adu;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{req}, adu, nullptr));
    QCOMPARE(adu.size(), 8);

    ModbusRtuPdu out;
    QVERIFY(decodeModbusRtu(BusDirection::Downstream,
                            reinterpret_cast<const std::uint8_t*>(adu.constData()),
                            static_cast<std::size_t>(adu.size()),
                            out,
                            nullptr));
    const auto* back = std::get_if<ReadHoldingRegistersRequest>(&out);
    QVERIFY(back);
    QCOMPARE(static_cast<int>(back->unitId), 1);
    QCOMPARE(static_cast<int>(back->startAddress), 40001);
    QCOMPARE(static_cast<int>(back->quantity), 4);
}

void ModbusRtuPduCodecTest::roundTrip_readCoilsRequest()
{
    const ReadCoilsRequest req{0x01, 0x0010, 8};
    QByteArray             adu;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{req}, adu, nullptr));
    QCOMPARE(adu.size(), 8);

    ModbusRtuPdu out;
    QVERIFY(decodeModbusRtu(BusDirection::Downstream,
                            reinterpret_cast<const std::uint8_t*>(adu.constData()),
                            static_cast<std::size_t>(adu.size()),
                            out,
                            nullptr));
    const auto* back = std::get_if<ReadCoilsRequest>(&out);
    QVERIFY(back);
    QCOMPARE(static_cast<int>(back->startAddress), 0x10);
    QCOMPARE(static_cast<int>(back->quantity), 8);
}

void ModbusRtuPduCodecTest::roundTrip_readCoilsResponse()
{
    ReadCoilsResponse resp;
    resp.unitId = 0x01;
    resp.coils  = QVector<std::uint8_t>{0x55, 0x01};
    QByteArray adu;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{resp}, adu, nullptr));

    ModbusRtuPdu out;
    QVERIFY(decodeModbusRtu(BusDirection::Upstream,
                            reinterpret_cast<const std::uint8_t*>(adu.constData()),
                            static_cast<std::size_t>(adu.size()),
                            out,
                            nullptr));
    const auto* back = std::get_if<ReadCoilsResponse>(&out);
    QVERIFY(back);
    QCOMPARE(back->coils.size(), 2);
    QCOMPARE(static_cast<int>(back->coils[0]), 0x55);
}

void ModbusRtuPduCodecTest::roundTrip_readDiscreteInputsRequest()
{
    const ReadDiscreteInputsRequest req{0x01, 0x0020, 16};
    QByteArray                      adu;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{req}, adu, nullptr));

    ModbusRtuPdu out;
    QVERIFY(decodeModbusRtu(BusDirection::Downstream,
                            reinterpret_cast<const std::uint8_t*>(adu.constData()),
                            static_cast<std::size_t>(adu.size()),
                            out,
                            nullptr));
    const auto* back = std::get_if<ReadDiscreteInputsRequest>(&out);
    QVERIFY(back);
    QCOMPARE(static_cast<int>(back->startAddress), 0x20);
    QCOMPARE(static_cast<int>(back->quantity), 16);
}

void ModbusRtuPduCodecTest::roundTrip_readDiscreteInputsResponse()
{
    ReadDiscreteInputsResponse resp;
    resp.unitId         = 0x01;
    resp.discreteInputs = QVector<std::uint8_t>{0x0F};
    QByteArray adu;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{resp}, adu, nullptr));

    ModbusRtuPdu out;
    QVERIFY(decodeModbusRtu(BusDirection::Upstream,
                            reinterpret_cast<const std::uint8_t*>(adu.constData()),
                            static_cast<std::size_t>(adu.size()),
                            out,
                            nullptr));
    const auto* back = std::get_if<ReadDiscreteInputsResponse>(&out);
    QVERIFY(back);
    QCOMPARE(back->discreteInputs.size(), 1);
    QCOMPARE(static_cast<int>(back->discreteInputs[0]), 0x0F);
}

void ModbusRtuPduCodecTest::roundTrip_readHoldingRegistersResponse()
{
    ReadHoldingRegistersResponse resp;
    resp.unitId    = 0x02;
    resp.registers = QVector<std::uint16_t>{0x00FF, 0x1388};

    QByteArray adu;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{resp}, adu, nullptr));

    ModbusRtuPdu out;
    QVERIFY(decodeModbusRtu(BusDirection::Upstream,
                            reinterpret_cast<const std::uint8_t*>(adu.constData()),
                            static_cast<std::size_t>(adu.size()),
                            out,
                            nullptr));
    const auto* back = std::get_if<ReadHoldingRegistersResponse>(&out);
    QVERIFY(back);
    QCOMPARE(static_cast<int>(back->unitId), 2);
    QCOMPARE(back->registers.size(), 2);
    QCOMPARE(static_cast<int>(back->registers[0]), 255);
    QCOMPARE(static_cast<int>(back->registers[1]), 5000);
}

void ModbusRtuPduCodecTest::roundTrip_exception()
{
    const ModbusExceptionPdu ex{0x05, static_cast<std::uint8_t>(0x83), 0x02};
    QByteArray               adu;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{ex}, adu, nullptr));
    QCOMPARE(adu.size(), 5);

    ModbusRtuPdu out;
    QVERIFY(decodeModbusRtu(BusDirection::Upstream,
                            reinterpret_cast<const std::uint8_t*>(adu.constData()),
                            static_cast<std::size_t>(adu.size()),
                            out,
                            nullptr));
    const auto* back = std::get_if<ModbusExceptionPdu>(&out);
    QVERIFY(back);
    QCOMPARE(static_cast<int>(back->unitId), 5);
    QCOMPARE(static_cast<int>(back->function), 0x83);
    QCOMPARE(static_cast<int>(back->exceptionCode), 2);
}

void ModbusRtuPduCodecTest::unknownDirection_heuristic_request_vs_response()
{
    ReadHoldingRegistersRequest req{0x01, 0, 1};
    QByteArray                  aduReq;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{req}, aduReq, nullptr));

    ModbusRtuPdu asReq;
    QVERIFY(decodeModbusRtu(BusDirection::Unknown,
                            reinterpret_cast<const std::uint8_t*>(aduReq.constData()),
                            static_cast<std::size_t>(aduReq.size()),
                            asReq,
                            nullptr));
    QVERIFY(std::holds_alternative<ReadHoldingRegistersRequest>(asReq));

    ReadHoldingRegistersResponse resp;
    resp.unitId    = 1;
    resp.registers = {0x00AA};
    QByteArray aduResp;
    QVERIFY(encodeModbusRtu(ModbusRtuPdu{resp}, aduResp, nullptr));

    ModbusRtuPdu asResp;
    QVERIFY(decodeModbusRtu(BusDirection::Unknown,
                            reinterpret_cast<const std::uint8_t*>(aduResp.constData()),
                            static_cast<std::size_t>(aduResp.size()),
                            asResp,
                            nullptr));
    QVERIFY(std::holds_alternative<ReadHoldingRegistersResponse>(asResp));
}

QTEST_MAIN(ModbusRtuPduCodecTest)
#include "modbus_rtu_pdu_codec_test.moc"
