#include <catch2/catch_test_macros.hpp>

#include "fsfw/ipc/QueueFactory.h"
#include "fsfw/storagemanager/LocalPool.h"
#include "fsfw/storagemanager/PoolManager.h"
#include "mock/AcceptsTmMock.h"
#include "mock/CdsShortTimestamperMock.h"
#include "mock/InternalErrorReporterMock.h"
#include "mock/MessageQueueMock.h"
#include "mock/PusDistributorMock.h"
#include "mock/PusServiceBaseMock.h"
#include "mock/PusVerificationReporterMock.h"

TEST_CASE("Pus Service Base", "[pus-service-base]") {
  uint16_t apid = 2;
  auto verificationReporter = PusVerificationReporterMock();
  auto msgQueue = MessageQueueMock(1, MessageQueueIF::NO_QUEUE);
  auto tmReceiver = AcceptsTmMock(2);
  auto psbParams = PsbParams(0, apid, 17);

  LocalPool::LocalPoolConfig cfg = {{5, 32}, {2, 64}};
  LocalPool pool(objects::NO_OBJECT, cfg);

  psbParams.verifReporter = &verificationReporter;
  psbParams.reqQueue = &msgQueue;
  psbParams.tmReceiver = &tmReceiver;
  psbParams.tcPool = &pool;
  auto psb = PsbMock(psbParams);

  store_address_t storeId;
  TmTcMessage tmtcMsg;

  // Components to create valid PUS packets
  auto packetId = PacketId(ccsds::PacketType::TC, true, 0x02);
  auto spParams =
      SpacePacketParams(packetId, PacketSeqCtrl(ccsds::SequenceFlags::UNSEGMENTED, 0x34), 0x00);
  auto pusParams = PusTcParams(17, 1);
  PusTcCreator creator(spParams, pusParams);

  REQUIRE(psb.initialize() == returnvalue::OK);

  SECTION("State") {
    REQUIRE(psb.getIdentifier() == 17);
    REQUIRE(psb.getObjectId() == 0);
    REQUIRE(psb.getRequestQueue() == msgQueue.getId());
    auto psbParamsLocal = psb.getParams();
    REQUIRE(psbParamsLocal.errReporter == nullptr);
  }

  SECTION("Perform Service") {
    REQUIRE(psb.performServiceCallCnt == 0);
    REQUIRE(psb.performOperation(0) == returnvalue::OK);
    REQUIRE(psb.performServiceCallCnt == 1);
  }

  SECTION("Send Request with Successful Handling") {
    REQUIRE(psb.performServiceCallCnt == 0);
    uint8_t* dataPtr;
    REQUIRE(pool.getFreeElement(&storeId, creator.getSerializedSize(), &dataPtr) ==
            returnvalue::OK);
    size_t serLen = 0;
    REQUIRE(creator.serializeBe(dataPtr, serLen, creator.getSerializedSize()) == returnvalue::OK);
    tmtcMsg.setStorageId(storeId);
    msgQueue.addReceivedMessage(tmtcMsg);
    REQUIRE(psb.performOperation(0) == returnvalue::OK);
    uint8_t subservice = 0;
    REQUIRE(psb.getAndPopNextSubservice(subservice));
    REQUIRE(subservice == 1);
    REQUIRE(psb.performServiceCallCnt == 1);
    // PSB should take care of freeing the pool slot
    REQUIRE(not pool.hasDataAtId(storeId));
    REQUIRE(verificationReporter.successCallCount() == 1);
    REQUIRE(verificationReporter.failCallCount() == 0);
    auto verifParams = verificationReporter.getNextSuccessCallParams();
    REQUIRE(verifParams.tcPacketId == creator.getPacketIdRaw());
    REQUIRE(verifParams.tcPsc == creator.getPacketSeqCtrlRaw());
  }

  SECTION("Send Request with Failed Handling") {
    uint8_t* dataPtr;
    REQUIRE(pool.getFreeElement(&storeId, creator.getSerializedSize(), &dataPtr) ==
            returnvalue::OK);
    size_t serLen = 0;
    REQUIRE(creator.serializeBe(dataPtr, serLen, creator.getSerializedSize()) == returnvalue::OK);
    tmtcMsg.setStorageId(storeId);
    msgQueue.addReceivedMessage(tmtcMsg);
    psb.makeNextHandleReqCallFail(3);
    REQUIRE(psb.performOperation(0) == returnvalue::OK);
    uint8_t subservice = 0;
    REQUIRE(psb.getAndPopNextSubservice(subservice));
    REQUIRE(subservice == 1);
    REQUIRE(psb.performServiceCallCnt == 1);
    // PSB should take care of freeing the pool slot
    REQUIRE(not pool.hasDataAtId(storeId));
    REQUIRE(verificationReporter.successCallCount() == 0);
    REQUIRE(verificationReporter.failCallCount() == 1);
    auto verifParams = verificationReporter.getNextFailCallParams();
    REQUIRE(verifParams.tcPacketId == creator.getPacketIdRaw());
    REQUIRE(verifParams.tcPsc == creator.getPacketSeqCtrlRaw());
  }

  SECTION("Invalid Packet Sent") {
    tmtcMsg.setStorageId(store_address_t::invalid());
    msgQueue.addReceivedMessage(tmtcMsg);
    REQUIRE(psb.performOperation(0) == returnvalue::OK);
    REQUIRE(verificationReporter.failCallCount() == 1);
    auto verifParams = verificationReporter.getNextFailCallParams();
    REQUIRE(verifParams.tcPacketId == 0);
    REQUIRE(verifParams.tcPsc == 0);
  }

  SECTION("Set Verif Reporter") {
    auto verificationReporter2 = PusVerificationReporterMock();
    psb.setVerificationReporter(verificationReporter2);
    auto& p = psb.getParams();
    REQUIRE(p.verifReporter == &verificationReporter2);
  }

  SECTION("Set Request Queue") {
    auto msgQueueMock = MessageQueueMock(2, MessageQueueIF::NO_QUEUE);
    psb.setRequestQueue(msgQueueMock);
    auto& p = psb.getParams();
    REQUIRE(p.reqQueue == &msgQueueMock);
  }

  SECTION("Set TM Receiver") {
    auto tmReceiver2 = AcceptsTmMock(3);
    psb.setTmReceiver(tmReceiver2);
    auto& p = psb.getParams();
    REQUIRE(p.tmReceiver == &tmReceiver2);
  }

  SECTION("Set TC Store") {
    LocalPool tcStore2(5, cfg);
    psb.setTcPool(tcStore2);
    auto& p = psb.getParams();
    REQUIRE(p.tcPool == &tcStore2);
  }

  SECTION("Set error reporter") {
    auto errReporter = InternalErrorReporterMock();
    psb.setErrorReporter(errReporter);
    auto& p = psb.getParams();
    REQUIRE(p.errReporter == &errReporter);
  }
  SECTION("Owner Queue") {
    // This will cause the initialize function to create a new owner queue
    psbParams.reqQueue = nullptr;
    psbParams.objectId = 1;
    auto mockWithOwnerQueue = PsbMock(psbParams);
    REQUIRE(mockWithOwnerQueue.getRequestQueue() == MessageQueueIF::NO_QUEUE);
    REQUIRE(mockWithOwnerQueue.initialize() == returnvalue::OK);
    REQUIRE(mockWithOwnerQueue.getRequestQueue() != MessageQueueIF::NO_QUEUE);
  }

  SECTION("TM Store Helper Initializer") {
    TmStoreHelper storeHelper(0);
    psb.initializeTmStoreHelper(storeHelper);
    REQUIRE(storeHelper.getApid() == apid);
  }

  SECTION("TM Send Helper Initializer") {
    TmSendHelper sendHelper;
    psb.initializeTmSendHelper(sendHelper);
    REQUIRE(sendHelper.getMsgQueue() == &msgQueue);
    REQUIRE(sendHelper.getDefaultDestination() == msgQueue.getDefaultDestination());
  }

  SECTION("TM Store And Send Helper Initializer") {
    TmStoreHelper storeHelper(0);
    TmSendHelper sendHelper;
    psb.initializeTmHelpers(sendHelper, storeHelper);
    REQUIRE(sendHelper.getMsgQueue() == &msgQueue);
    REQUIRE(sendHelper.getDefaultDestination() == msgQueue.getDefaultDestination());
    REQUIRE(storeHelper.getApid() == apid);
  }

  SECTION("TM Send Helper Initializer With Error Reporter") {
    TmSendHelper sendHelper;
    auto errReporter = InternalErrorReporterMock();
    psb.setErrorReporter(errReporter);
    psb.initializeTmSendHelper(sendHelper);
    REQUIRE(sendHelper.getMsgQueue() == &msgQueue);
    REQUIRE(sendHelper.getDefaultDestination() == msgQueue.getDefaultDestination());
    REQUIRE(sendHelper.getInternalErrorReporter() == &errReporter);
  }

  SECTION("Auto Initialize PUS Distributor") {
    psbParams.objectId = 1;
    object_id_t distributorId = 3;
    auto psb2 = PsbMock(psbParams);
    auto pusDistrib = PusDistributorMock(distributorId);
    PsbMock::setStaticPusDistributor(distributorId);
    REQUIRE(PsbMock::getStaticPusDistributor() == distributorId);
    REQUIRE(psb2.initialize() == returnvalue::OK);
    REQUIRE(pusDistrib.registerCallCount == 1);
    REQUIRE(pusDistrib.registeredServies.front() == &psb2);
  }

  SECTION("Auto Initialize Packet Destination") {
    psbParams.tmReceiver = nullptr;
    psbParams.objectId = 1;
    object_id_t destId = 3;
    auto psb2 = PsbMock(psbParams);
    auto packetDest = AcceptsTmMock(destId, 2);
    PsbMock::setStaticTmDest(destId);
    REQUIRE(PsbMock::getStaticTmDest() == destId);
    REQUIRE(psb2.initialize() == returnvalue::OK);
    auto& p = psb2.getParams();
    REQUIRE(p.tmReceiver == &packetDest);
  }

  SECTION("Auto Initialize Verification Reporter") {
    psbParams.verifReporter = nullptr;
    psbParams.objectId = 1;
    object_id_t reporterId = objects::VERIFICATION_REPORTER;
    PusVerificationReporterMock otherReporter(reporterId);
    auto psb2 = PsbMock(psbParams);
    REQUIRE(psb2.initialize() == returnvalue::OK);
    auto& p = psb2.getParams();
    REQUIRE(p.verifReporter == &otherReporter);
  }

  SECTION("Auto Initialize TC Pool") {
    PoolManager tcStoreGlobal(objects::TC_STORE, cfg);
    psbParams.tcPool = nullptr;
    psbParams.objectId = 1;
    auto psb2 = PsbMock(psbParams);
    REQUIRE(psb2.initialize() == returnvalue::OK);
    auto& p = psb2.getParams();
    REQUIRE(p.tcPool == &tcStoreGlobal);
  }

  SECTION("Invalid Verification Reporter") {
    psbParams.verifReporter = nullptr;
    psbParams.objectId = 1;
    auto psb2 = PsbMock(psbParams);
    REQUIRE(psb2.initialize() == ObjectManagerIF::CHILD_INIT_FAILED);
  }

  SECTION("Invalid TC Store") {
    psbParams.tcPool = nullptr;
    psbParams.objectId = 1;
    auto psb2 = PsbMock(psbParams);
    REQUIRE(psb2.initialize() == ObjectManagerIF::CHILD_INIT_FAILED);
  }
}