#include "dbus_environment.hpp"
#include "fakes/clock_fake.hpp"
#include "helpers.hpp"
#include "messages/collect_trigger_id.hpp"
#include "messages/trigger_presence_changed_ind.hpp"
#include "messages/update_report_ind.hpp"
#include "mocks/json_storage_mock.hpp"
#include "mocks/metric_mock.hpp"
#include "mocks/report_factory_mock.hpp"
#include "mocks/report_manager_mock.hpp"
#include "params/report_params.hpp"
#include "report.hpp"
#include "report_manager.hpp"
#include "utils/clock.hpp"
#include "utils/contains.hpp"
#include "utils/conv_container.hpp"
#include "utils/dbus_path_utils.hpp"
#include "utils/messanger.hpp"
#include "utils/string_utils.hpp"
#include "utils/transform.hpp"
#include "utils/tstring.hpp"

#include <sdbusplus/exception.hpp>

#include <ranges>

using namespace testing;
using namespace std::literals::string_literals;
using namespace std::chrono_literals;
using sdbusplus::message::object_path;
namespace tstring = utils::tstring;

using ErrorMessageDbusType = std::tuple<std::string, std::string>;
using ErrorMessagesDbusType = std::vector<ErrorMessageDbusType>;

constexpr Milliseconds systemTimestamp = 55ms;

namespace
{

ReportParams defaultParams()
{
    return ReportParams();
}

ReportParams defaultOnChangeParams()
{
    return defaultParams().reportingType(ReportingType::onChange);
}

} // namespace

class TestReport : public Test
{
  public:
    std::unique_ptr<ReportManagerMock> reportManagerMock =
        std::make_unique<NiceMock<ReportManagerMock>>();
    std::unique_ptr<ReportFactoryMock> reportFactoryMock =
        std::make_unique<NiceMock<ReportFactoryMock>>();
    nlohmann::json storedConfiguration;
    NiceMock<StorageMock> storageMock;
    std::vector<std::shared_ptr<MetricMock>> metricMocks;
    std::unique_ptr<ClockFake> clockFakePtr = std::make_unique<ClockFake>();
    ClockFake& clockFake = *clockFakePtr;
    std::unique_ptr<Report> sut;
    utils::Messanger messanger;

    MockFunction<void()> checkPoint;

    TestReport() : messanger(DbusEnvironment::getIoc())
    {
        clockFake.system.set(systemTimestamp);
        ON_CALL(storageMock, store(to_file_path(ReportParams().reportId()), _))
            .WillByDefault(SaveArg<1>(&storedConfiguration));
    }

    void initMetricMocks(
        const std::vector<LabeledMetricParameters>& metricParameters)
    {
        for (auto i = metricMocks.size(); i < metricParameters.size(); ++i)
        {
            metricMocks.emplace_back(std::make_shared<NiceMock<MetricMock>>());
        }
        metricMocks.resize(metricParameters.size());

        std::vector<MetricValue> readings{
            {MetricValue{"b", 17.1, 114}, MetricValue{"bb", 42.0, 74}}};

        ASSERT_THAT(readings.size(), Ge(metricParameters.size()));

        for (size_t i = 0; i < metricParameters.size(); ++i)
        {
            ON_CALL(*metricMocks[i], getUpdatedReadings())
                .WillByDefault(ReturnRefOfCopy(std::vector({readings[i]})));
            ON_CALL(*metricMocks[i], dumpConfiguration())
                .WillByDefault(Return(metricParameters[i]));
        }
    }

    std::vector<std::shared_ptr<interfaces::Metric>>
        getMetricsFromReadingParams(const ReadingParameters& params)
    {
        const auto metricParameters =
            reportFactoryMock->convertMetricParams(params);
        std::vector<std::shared_ptr<MetricMock>> metricMocks;

        for (size_t i = 0; i < metricParameters.size(); ++i)
        {
            metricMocks.emplace_back(std::make_shared<NiceMock<MetricMock>>());
            ON_CALL(*metricMocks[i], dumpConfiguration())
                .WillByDefault(Return(metricParameters[i]));
            ON_CALL(*metricMocks[i], metricCount())
                .WillByDefault(Return(metricParameters[i]
                                          .at_label<tstring::SensorPath>()
                                          .size()));
        }

        return utils::convContainer<std::shared_ptr<interfaces::Metric>>(
            metricMocks);
    }

    void SetUp() override
    {
        sut = makeReport(defaultParams());
    }

    static interfaces::JsonStorage::FilePath to_file_path(std::string id)
    {
        return interfaces::JsonStorage::FilePath(
            std::to_string(std::hash<std::string>{}(id)));
    }

    std::unique_ptr<Report> makeReport(const ReportParams& params)
    {
        initMetricMocks(params.metricParameters());

        return std::make_unique<Report>(
            DbusEnvironment::getIoc(), DbusEnvironment::getObjServer(),
            params.reportId(), params.reportName(), params.reportingType(),
            params.reportActions(), params.interval(), params.appendLimit(),
            params.reportUpdates(), *reportManagerMock, storageMock,
            utils::convContainer<std::shared_ptr<interfaces::Metric>>(
                metricMocks),
            *reportFactoryMock, params.enabled(), std::move(clockFakePtr),
            params.readings());
    }

    template <class T>
    static T getProperty(const std::string& path, const std::string& property)
    {
        return DbusEnvironment::getProperty<T>(path, Report::reportIfaceName,
                                               property);
    }

    template <class T>
    static boost::system::error_code setProperty(const std::string& path,
                                                 const std::string& property,
                                                 const T& newValue)
    {
        return DbusEnvironment::setProperty<T>(path, Report::reportIfaceName,
                                               property, newValue);
    }

    template <class... Args>
    static boost::system::error_code callMethod(const std::string& path,
                                                const std::string& method,
                                                Args&&... args)
    {
        return DbusEnvironment::callMethod(path, Report::reportIfaceName,
                                           "SetReportingProperties",
                                           std::forward<Args>(args)...);
    }

    template <class T>
    struct ChangePropertyParams
    {
        Matcher<T> valueBefore = _;
        T newValue;
        Matcher<boost::system::error_code> ec =
            Eq(boost::system::errc::success);
        Matcher<T> valueAfter = Eq(newValue);
    };

    template <class T>
    static void changeProperty(const std::string& path,
                               const std::string& property,
                               ChangePropertyParams<T> p)
    {
        ASSERT_THAT(getProperty<T>(path, property), p.valueBefore);
        ASSERT_THAT(setProperty<T>(path, property, p.newValue), p.ec);
        EXPECT_THAT(getProperty<T>(path, property), p.valueAfter);
    }

    boost::system::error_code call(const std::string& path,
                                   const std::string& interface,
                                   const std::string& method)
    {
        std::promise<boost::system::error_code> methodPromise;
        DbusEnvironment::getBus()->async_method_call(
            [&methodPromise](boost::system::error_code ec) {
            methodPromise.set_value(ec);
        },
            DbusEnvironment::serviceName(), path, interface, method);
        return DbusEnvironment::waitForFuture(methodPromise.get_future());
    }

    boost::system::error_code update(const std::string& path)
    {
        return call(path, Report::reportIfaceName, "Update");
    }

    boost::system::error_code deleteReport(const std::string& path)
    {
        return call(path, Report::deleteIfaceName, "Delete");
    }

    static std::pair<std::string, std::vector<std::string>>
        makeStateDetail(const std::string& detailType,
                        std::vector<std::string> detailArgs)
    {
        return make_pair(detailType, detailArgs);
    }
};

TEST_F(TestReport, returnsId)
{
    EXPECT_THAT(sut->getId(), Eq(defaultParams().reportId()));
}

TEST_F(TestReport, verifyIfPropertiesHaveValidValue)
{
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"),
                Eq(defaultParams().enabled()));
    EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
                Eq(defaultParams().interval().count()));
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"), Eq(true));
    EXPECT_THAT(
        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
        Eq(utils::transform(defaultParams().reportActions(), [](const auto v) {
        return utils::enumToString(v);
    })));
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
                Eq(utils::contains(defaultParams().reportActions(),
                                   ReportAction::emitsReadingsUpdate)));
    EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "AppendLimit"),
                Eq(defaultParams().appendLimit()));
    EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportingType"),
                Eq(utils::enumToString(defaultParams().reportingType())));
    EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportUpdates"),
                Eq(utils::enumToString(defaultParams().reportUpdates())));
    EXPECT_THAT(
        getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
        Eq(utils::contains(defaultParams().reportActions(),
                           ReportAction::logToMetricReportsCollection)));
    EXPECT_THAT(
        getProperty<ReadingParameters>(sut->getPath(), "ReadingParameters"),
        Eq(toReadingParameters(defaultParams().metricParameters())));
    EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"),
                Eq(defaultParams().reportName()));
    EXPECT_THAT(
        getProperty<std::vector<object_path>>(sut->getPath(), "Triggers"),
        IsEmpty());
}

TEST_F(TestReport, readingsAreInitialyEmpty)
{
    EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
                Eq(Readings{}));
}

TEST_F(TestReport, setReadingParametersWithNewParams)
{
    ReadingParameters newParams = toReadingParameters(
        std::vector<LabeledMetricParameters>{{LabeledMetricParameters{
            {LabeledSensorInfo{"Service",
                               "/xyz/openbmc_project/sensors/power/psu",
                               "NewMetadata123"}},
            OperationType::avg,
            CollectionTimeScope::startup,
            CollectionDuration(250ms)}}});
    auto metrics = getMetricsFromReadingParams(newParams);

    EXPECT_CALL(*reportFactoryMock, updateMetrics(_, _, _))
        .WillOnce(SetArgReferee<0>(metrics));
    EXPECT_THAT(
        setProperty(sut->getPath(), "ReadingParameters", newParams).value(),
        Eq(boost::system::errc::success));
    EXPECT_THAT(
        getProperty<ReadingParameters>(sut->getPath(), "ReadingParameters"),
        Eq(newParams));
}

TEST_F(TestReport, setReadingParametersFailsWhenMetricCountExceedsAllowedValue)
{
    std::vector<LabeledMetricParameters> readingParams{{LabeledMetricParameters{
        {LabeledSensorInfo{"Service", "/xyz/openbmc_project/sensors/power/psu",
                           "NewMetadata123"}},
        OperationType::avg,
        CollectionTimeScope::startup,
        CollectionDuration(250ms)}}};

    auto& metricParamsVec =
        readingParams[0].at_label<utils::tstring::SensorPath>();
    for (size_t i = 0; i < ReportManager::maxNumberMetrics; i++)
    {
        metricParamsVec.emplace_back(LabeledSensorInfo{
            "Service", "/xyz/openbmc_project/sensors/power/psu",
            "NewMetadata123"});
    }

    ReadingParameters newParams = toReadingParameters(readingParams);

    EXPECT_THAT(
        setProperty(sut->getPath(), "ReadingParameters", newParams).value(),
        Eq(boost::system::errc::invalid_argument));
}

TEST_F(TestReport, setReportActionsWithValidNewActions)
{
    std::vector<std::string> newActions = {
        utils::enumToString(ReportAction::emitsReadingsUpdate)};
    std::vector<std::string> currActions =
        utils::transform(defaultParams().reportActions(),
                         [](const auto v) { return utils::enumToString(v); });

    EXPECT_THAT(newActions, Ne(currActions));
    EXPECT_THAT(
        setProperty(sut->getPath(), "ReportActions", newActions).value(),
        Eq(boost::system::errc::success));
    EXPECT_THAT(
        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
        UnorderedElementsAre(
            utils::enumToString(ReportAction::emitsReadingsUpdate),
            utils::enumToString(ReportAction::logToMetricReportsCollection)));
}

TEST_F(TestReport, setReportActionsWithValidUnsortedActions)
{
    std::vector<std::string> newActions = {
        utils::enumToString(ReportAction::logToMetricReportsCollection),
        utils::enumToString(ReportAction::emitsReadingsUpdate)};
    std::vector<std::string> expectedActions = {
        utils::enumToString(ReportAction::emitsReadingsUpdate),
        utils::enumToString(ReportAction::logToMetricReportsCollection)};
    std::vector<std::string> currActions =
        utils::transform(defaultParams().reportActions(),
                         [](const auto v) { return utils::enumToString(v); });

    EXPECT_THAT(newActions, Ne(currActions));
    EXPECT_THAT(
        setProperty(sut->getPath(), "ReportActions", newActions).value(),
        Eq(boost::system::errc::success));
    EXPECT_THAT(
        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
        Eq(expectedActions));
}

TEST_F(TestReport, setReportActionsWithEmptyActions)
{
    std::vector<std::string> newActions = {};
    std::vector<std::string> expectedActions = {
        utils::enumToString(ReportAction::logToMetricReportsCollection)};
    std::vector<std::string> currActions =
        utils::transform(defaultParams().reportActions(),
                         [](const auto v) { return utils::enumToString(v); });

    EXPECT_THAT(newActions, Ne(currActions));
    EXPECT_THAT(
        setProperty(sut->getPath(), "ReportActions", newActions).value(),
        Eq(boost::system::errc::success));
    EXPECT_THAT(
        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
        Eq(expectedActions));
}

TEST_F(TestReport, setReportActionsWithInvalidActions)
{
    std::vector<std::string> invalidActions = {"EmitsReadingsUpdate_1"};
    EXPECT_THAT(
        setProperty(sut->getPath(), "ReportActions", invalidActions).value(),
        Eq(boost::system::errc::invalid_argument));
    EXPECT_THAT(
        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
        Eq(utils::transform(defaultParams().reportActions(), [](const auto v) {
        return utils::enumToString(v);
    })));
}

TEST_F(TestReport, createReportWithEmptyActions)
{
    std::vector<std::string> expectedActions = {
        utils::enumToString(ReportAction::logToMetricReportsCollection)};

    sut = makeReport(ReportParams().reportId("TestId_1").reportActions({}));
    EXPECT_THAT(
        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
        Eq(expectedActions));
}

TEST_F(TestReport, createReportWithValidUnsortedActions)
{
    std::vector<std::string> newActions = {
        utils::enumToString(ReportAction::logToMetricReportsCollection),
        utils::enumToString(ReportAction::emitsReadingsUpdate)};
    std::vector<std::string> expectedActions = {
        utils::enumToString(ReportAction::emitsReadingsUpdate),
        utils::enumToString(ReportAction::logToMetricReportsCollection)};

    sut = makeReport(
        ReportParams()
            .reportId("TestId_1")
            .reportActions(utils::transform(newActions, [](const auto& action) {
        return utils::toReportAction(action);
    })));
    EXPECT_THAT(
        getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
        Eq(expectedActions));
}

TEST_F(TestReport, setEnabledWithNewValue)
{
    bool newValue = !defaultParams().enabled();
    EXPECT_THAT(setProperty(sut->getPath(), "Enabled", newValue).value(),
                Eq(boost::system::errc::success));
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"), Eq(newValue));
}

TEST_F(TestReport, setReportingPropertiesWithValidValues)
{
    uint64_t newValue = ReportManager::minInterval.count() * 42;
    EXPECT_THAT(callMethod(sut->getPath(), "SetReportingProperties",
                           utils::enumToString(ReportingType::periodic),
                           newValue),
                Eq(boost::system::errc::success));
    EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
                Eq(newValue));
}

TEST_F(TestReport, failsToSetInvalidInterval)
{
    uint64_t newValue = ReportManager::minInterval.count() - 1;

    EXPECT_THAT(
        callMethod(sut->getPath(), "SetReportingProperties", "", newValue),
        Eq(boost::system::errc::invalid_argument));

    EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
                Eq(defaultParams().interval().count()));
}

TEST_F(TestReport, failsToSetIncompatibleInterval)
{
    auto report = makeReport(defaultParams()
                                 .reportId("report2")
                                 .reportingType(ReportingType::onRequest)
                                 .interval(Milliseconds{0}));

    uint64_t newValue = ReportManager::minInterval.count();

    EXPECT_THAT(
        callMethod(report->getPath(), "SetReportingProperties", "", newValue),
        Eq(boost::system::errc::invalid_argument));

    EXPECT_THAT(getProperty<uint64_t>(report->getPath(), "Interval"),
                Eq(defaultParams().interval().count()));
}

TEST_F(TestReport, failsToSetInvalidReportingType)
{
    auto report = makeReport(defaultParams()
                                 .reportId("report2")
                                 .reportingType(ReportingType::onRequest)
                                 .interval(Milliseconds{0}));

    EXPECT_THAT(callMethod(sut->getPath(), "SetReportingProperties", "XYZ",
                           std::numeric_limits<uint64_t>::max()),
                Eq(boost::system::errc::invalid_argument));

    EXPECT_THAT(getProperty<std::string>(report->getPath(), "ReportingType"),
                Eq(utils::enumToString(ReportingType::onRequest)));
}

TEST_F(TestReport, failsToSetIncompatibleReportingType)
{
    auto report = makeReport(defaultParams()
                                 .reportId("report2")
                                 .reportingType(ReportingType::onRequest)
                                 .interval(Milliseconds{0}));

    EXPECT_THAT(callMethod(sut->getPath(), "SetReportingProperties",
                           utils::enumToString(ReportingType::periodic),
                           std::numeric_limits<uint64_t>::max()),
                Eq(boost::system::errc::invalid_argument));

    EXPECT_THAT(getProperty<std::string>(report->getPath(), "ReportingType"),
                Eq(utils::enumToString(ReportingType::onRequest)));
}

TEST_F(TestReport, settingEmitsReadingsUpdateHaveNoEffect)
{
    EXPECT_THAT(
        setProperty(sut->getPath(), "EmitsReadingsUpdate", true).value(),
        Eq(boost::system::errc::read_only_file_system));
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
                Eq(utils::contains(defaultParams().reportActions(),
                                   ReportAction::emitsReadingsUpdate)));
}

TEST_F(TestReport, settingLogToMetricReportCollectionHaveNoEffect)
{
    EXPECT_THAT(
        setProperty(sut->getPath(), "LogToMetricReportsCollection", true)
            .value(),
        Eq(boost::system::errc::read_only_file_system));
    EXPECT_THAT(
        getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
        Eq(utils::contains(defaultParams().reportActions(),
                           ReportAction::logToMetricReportsCollection)));
}

TEST_F(TestReport, settingPersistencyToFalseRemovesReportFromStorage)
{
    EXPECT_CALL(storageMock, store(_, _)).Times(0);
    EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())))
        .Times(AtLeast(1));

    bool persistency = false;
    EXPECT_THAT(setProperty(sut->getPath(), "Persistency", persistency).value(),
                Eq(boost::system::errc::success));
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"),
                Eq(persistency));
}

TEST_F(TestReport, deleteReport)
{
    EXPECT_CALL(*reportManagerMock, removeReport(sut.get()));
    auto ec = deleteReport(sut->getPath());
    EXPECT_THAT(ec, Eq(boost::system::errc::success));
}

TEST_F(TestReport, deletingNonExistingReportReturnInvalidRequestDescriptor)
{
    auto ec =
        deleteReport(utils::constants::reportDirPath.str + "NonExisting"s);
    EXPECT_THAT(ec.value(), Eq(EBADR));
}

TEST_F(TestReport, deleteReportExpectThatFileIsRemoveFromStorage)
{
    EXPECT_CALL(storageMock, store(_, _)).Times(0);
    EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())))
        .Times(AtLeast(1));

    auto ec = deleteReport(sut->getPath());
    EXPECT_THAT(ec, Eq(boost::system::errc::success));
}

TEST_F(TestReport, updatesTriggerIdWhenTriggerIsAdded)
{
    utils::Messanger messanger(DbusEnvironment::getIoc());

    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger2", {"someOtherReport"}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist,
        "trigger3",
        {"someOtherReport", defaultParams().reportId()}});

    EXPECT_THAT(
        getProperty<std::vector<object_path>>(sut->getPath(), "Triggers"),
        UnorderedElementsAre(utils::constants::triggerDirPath / "trigger1",
                             utils::constants::triggerDirPath / "trigger3"));
}

TEST_F(TestReport, updatesTriggerIdWhenTriggerIsRemoved)
{
    utils::Messanger messanger(DbusEnvironment::getIoc());

    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger2", {defaultParams().reportId()}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger3", {defaultParams().reportId()}});

    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Removed, "trigger1", {defaultParams().reportId()}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Removed, "trigger2", {}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Removed, "trigger1", {defaultParams().reportId()}});

    EXPECT_THAT(
        getProperty<std::vector<object_path>>(sut->getPath(), "Triggers"),
        UnorderedElementsAre(utils::constants::triggerDirPath / "trigger3"));
}

TEST_F(TestReport, updatesTriggerIdWhenTriggerIsModified)
{
    utils::Messanger messanger(DbusEnvironment::getIoc());

    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger2", {defaultParams().reportId()}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger3", {defaultParams().reportId()}});

    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger2", {}});
    messanger.send(messages::TriggerPresenceChangedInd{
        messages::Presence::Exist, "trigger3", {defaultParams().reportId()}});

    EXPECT_THAT(
        getProperty<std::vector<object_path>>(sut->getPath(), "Triggers"),
        UnorderedElementsAre(utils::constants::triggerDirPath / "trigger1",
                             utils::constants::triggerDirPath / "trigger3"));
}

class TestReportStore :
    public TestReport,
    public WithParamInterface<std::pair<std::string, nlohmann::json>>
{
    void SetUp() override {}
};

INSTANTIATE_TEST_SUITE_P(
    _, TestReportStore,
    Values(
        std::make_pair("Enabled"s, nlohmann::json(defaultParams().enabled())),
        std::make_pair("Version"s, nlohmann::json(7)),
        std::make_pair("Id"s, nlohmann::json(defaultParams().reportId())),
        std::make_pair("Name"s, nlohmann::json(defaultParams().reportName())),
        std::make_pair("ReportingType",
                       nlohmann::json(defaultParams().reportingType())),
        std::make_pair("ReportActions", nlohmann::json(utils::transform(
                                            defaultParams().reportActions(),
                                            [](const auto v) {
    return utils::toUnderlying(v);
}))),
        std::make_pair("Interval",
                       nlohmann::json(defaultParams().interval().count())),
        std::make_pair("AppendLimit",
                       nlohmann::json(ReportParams().appendLimit())),
        std::make_pair(
            "ReadingParameters",
            nlohmann::json(
                {{{tstring::SensorPath::str(),
                   {{{tstring::Service::str(), "Service"},
                     {tstring::Path::str(),
                      "/xyz/openbmc_project/sensors/power/p1"},
                     {tstring::Metadata::str(), "metadata1"}}}},
                  {tstring::OperationType::str(), OperationType::avg},
                  {tstring::CollectionTimeScope::str(),
                   CollectionTimeScope::point},
                  {tstring::CollectionDuration::str(), 0}},
                 {{tstring::SensorPath::str(),
                   {{{tstring::Service::str(), "Service"},
                     {tstring::Path::str(),
                      "/xyz/openbmc_project/sensors/power/p2"},
                     {tstring::Metadata::str(), "metadata2"}}}},
                  {tstring::OperationType::str(), OperationType::avg},
                  {tstring::CollectionTimeScope::str(),
                   CollectionTimeScope::point},
                  {tstring::CollectionDuration::str(), 0}}}))));

TEST_P(TestReportStore, settingPersistencyToTrueStoresReport)
{
    sut = makeReport(defaultParams());

    {
        InSequence seq;
        EXPECT_CALL(storageMock, remove(to_file_path(sut->getId())));
        EXPECT_CALL(checkPoint, Call());
        EXPECT_CALL(storageMock, store(to_file_path(sut->getId()), _));
    }

    setProperty(sut->getPath(), "Persistency", false);
    checkPoint.Call();
    setProperty(sut->getPath(), "Persistency", true);

    const auto& [key, value] = GetParam();

    ASSERT_THAT(storedConfiguration.at(key), Eq(value));
}

TEST_P(TestReportStore, reportIsSavedToStorageAfterCreated)
{
    EXPECT_CALL(storageMock,
                store(to_file_path(defaultParams().reportId()), _));

    sut = makeReport(defaultParams());

    const auto& [key, value] = GetParam();

    ASSERT_THAT(storedConfiguration.at(key), Eq(value));
}

class TestReportValidNames :
    public TestReport,
    public WithParamInterface<ReportParams>
{
  public:
    void SetUp() override {}
};

INSTANTIATE_TEST_SUITE_P(
    ValidNames, TestReportValidNames,
    Values(defaultParams().reportName("Valid_1"),
           defaultParams().reportName("Valid_1/Valid_2"),
           defaultParams().reportName("Valid_1/Valid_2/Valid_3")));

TEST_P(TestReportValidNames, reportCtorDoesNotThrowOnValidName)
{
    EXPECT_NO_THROW(makeReport(GetParam()));
}

class TestReportInvalidIds :
    public TestReport,
    public WithParamInterface<ReportParams>
{
  public:
    void SetUp() override {}
};

INSTANTIATE_TEST_SUITE_P(InvalidNames, TestReportInvalidIds,
                         Values(defaultParams().reportId("/"),
                                defaultParams().reportId("/Invalid"),
                                defaultParams().reportId("Invalid/"),
                                defaultParams().reportId("Invalid/Invalid/"),
                                defaultParams().reportId("Invalid?")));

TEST_P(TestReportInvalidIds, failsToCreateReportWithInvalidName)
{
    EXPECT_CALL(storageMock, store).Times(0);

    EXPECT_THROW(makeReport(GetParam()), sdbusplus::exception::SdBusError);
}

class TestReportAllReportTypes :
    public TestReport,
    public WithParamInterface<ReportParams>
{
  public:
    void SetUp() override
    {
        sut = makeReport(GetParam());
    }
};

INSTANTIATE_TEST_SUITE_P(
    _, TestReportAllReportTypes,
    Values(defaultParams().reportingType(ReportingType::onRequest),
           defaultParams().reportingType(ReportingType::onChange),
           defaultParams()
               .reportingType(ReportingType::periodic)
               .interval(ReportManager::minInterval)));

TEST_P(TestReportAllReportTypes, returnPropertValueOfReportType)
{
    EXPECT_THAT(utils::toReportingType(
                    getProperty<std::string>(sut->getPath(), "ReportingType")),
                Eq(GetParam().reportingType()));
}

TEST_P(TestReportAllReportTypes, readingsAreUpdated)
{
    clockFake.system.advance(10ms);

    messanger.send(messages::UpdateReportInd{{sut->getId()}});
    const auto [timestamp, readings] = getProperty<Readings>(sut->getPath(),
                                                             "Readings");

    EXPECT_THAT(Milliseconds{timestamp}, Eq(systemTimestamp + 10ms));
}

TEST_P(TestReportAllReportTypes, readingsAreNotUpdatedWhenReportIsDisabled)
{
    clockFake.system.advance(10ms);

    setProperty(sut->getPath(), "Enabled", false);
    messanger.send(messages::UpdateReportInd{{sut->getId()}});
    const auto [timestamp, readings] = getProperty<Readings>(sut->getPath(),
                                                             "Readings");

    EXPECT_THAT(Milliseconds{timestamp}, Eq(0ms));
}

TEST_P(TestReportAllReportTypes, readingsAreNotUpdatedWhenReportIdDiffers)
{
    clockFake.system.advance(10ms);

    messanger.send(messages::UpdateReportInd{{sut->getId() + "x"s}});
    const auto [timestamp, readings] = getProperty<Readings>(sut->getPath(),
                                                             "Readings");

    EXPECT_THAT(Milliseconds{timestamp}, Eq(0ms));
}

class TestReportOnRequestType : public TestReport
{
    void SetUp() override
    {
        sut =
            makeReport(defaultParams().reportingType(ReportingType::onRequest));
    }
};

TEST_F(TestReportOnRequestType, updatesReadingTimestamp)
{
    clockFake.system.advance(10ms);

    ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));

    const auto [timestamp, readings] = getProperty<Readings>(sut->getPath(),
                                                             "Readings");

    EXPECT_THAT(Milliseconds{timestamp}, Eq(systemTimestamp + 10ms));
}

TEST_F(TestReportOnRequestType, updatesReadingWhenUpdateIsCalled)
{
    ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));

    const auto [timestamp, readings] = getProperty<Readings>(sut->getPath(),
                                                             "Readings");

    EXPECT_THAT(readings, ElementsAre(std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)));
}

class TestReportNonOnRequestType :
    public TestReport,
    public WithParamInterface<ReportParams>
{
    void SetUp() override
    {
        sut = makeReport(GetParam());
    }
};

INSTANTIATE_TEST_SUITE_P(_, TestReportNonOnRequestType,
                         Values(defaultParams()
                                    .reportingType(ReportingType::periodic)
                                    .interval(ReportManager::minInterval * 10),
                                defaultParams()
                                    .reportingType(ReportingType::onChange)
                                    .interval(Milliseconds(0))));

TEST_P(TestReportNonOnRequestType, readingsAreNotUpdateOnUpdateCall)
{
    ASSERT_THAT(update(sut->getPath()), Eq(boost::system::errc::success));

    EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
                Eq(Readings{}));
}

class TestReportNonPeriodicReport :
    public TestReport,
    public WithParamInterface<ReportParams>
{
  public:
    void SetUp() override
    {
        sut = makeReport(GetParam());
    }
};

INSTANTIATE_TEST_SUITE_P(
    _, TestReportNonPeriodicReport,
    Values(defaultParams().reportingType(ReportingType::onRequest),
           defaultParams().reportingType(ReportingType::onChange)));

TEST_P(TestReportNonPeriodicReport, readingsAreNotUpdatedAfterIntervalExpires)
{
    DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);

    EXPECT_THAT(getProperty<Readings>(sut->getPath(), "Readings"),
                Eq(Readings{}));
}

class TestReportPeriodicReport : public TestReport
{
    void SetUp() override
    {
        sut = makeReport(defaultParams()
                             .appendLimit(2u)
                             .reportingType(ReportingType::periodic)
                             .interval(ReportManager::minInterval));
    }
};

TEST_F(TestReportPeriodicReport, readingTimestampIsUpdatedAfterIntervalExpires)
{
    clockFake.system.advance(10ms);
    DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);

    const auto [timestamp, readings] = getProperty<Readings>(sut->getPath(),
                                                             "Readings");

    EXPECT_THAT(Milliseconds{timestamp}, Eq(systemTimestamp + 10ms));
}

TEST_F(TestReportPeriodicReport, readingsAreUpdatedAfterIntervalExpires)
{
    DbusEnvironment::sleepFor(ReportManager::minInterval + 1ms);

    const auto [timestamp, readings] = getProperty<Readings>(sut->getPath(),
                                                             "Readings");

    EXPECT_THAT(readings, ElementsAre(std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)));
}

struct ReportUpdatesReportParams
{
    ReportParams reportParams;
    std::vector<ReadingData> expectedReadings;
    bool expectedEnabled;

    friend void PrintTo(const ReportUpdatesReportParams& params,
                        std::ostream* os)
    {
        *os << "{ ReportParams: ";
        PrintTo(params.reportParams, os);
        *os << ", ExpectedReadings: ";
        PrintTo(params.expectedReadings, os);
        *os << ", ExpectedEnabled: " << params.expectedEnabled << " }";
    }
};

class TestReportWithReportUpdatesAndLimit :
    public TestReport,
    public WithParamInterface<ReportUpdatesReportParams>
{
  public:
    void SetUp() override {}

    void changeReport(ReportingType rt, Milliseconds interval)
    {
        callMethod(sut->getPath(), "SetReportingProperties",
                   utils::enumToString(rt), interval.count());
    }

    auto readings()
    {
        auto [timestamp, readings] = getProperty<Readings>(sut->getPath(),
                                                           "Readings");
        return readings;
    }

    void updateReportFourTimes()
    {
        for (int i = 0; i < 4; i++)
        {
            messanger.send(messages::UpdateReportInd{{sut->getId()}});
        }
    }
};

INSTANTIATE_TEST_SUITE_P(
    _, TestReportWithReportUpdatesAndLimit,
    Values(
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::appendWrapsWhenFull)
                .appendLimit(5),
            std::vector<ReadingData>{{std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u)}},
            true},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::appendWrapsWhenFull)
                .appendLimit(4),
            std::vector<ReadingData>{{std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)}},
            true},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::appendWrapsWhenFull)
                .appendLimit(0),
            std::vector<ReadingData>{}, true},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::appendStopsWhenFull)
                .appendLimit(10),
            std::vector<ReadingData>{{std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)}},
            true},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::appendStopsWhenFull)
                .appendLimit(5),
            std::vector<ReadingData>{{std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u)}},
            false},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::appendStopsWhenFull)
                .appendLimit(4),
            std::vector<ReadingData>{{std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u),
                                      std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)}},
            false},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::appendStopsWhenFull)
                .appendLimit(0),
            std::vector<ReadingData>{}, false},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::overwrite)
                .appendLimit(500),
            std::vector<ReadingData>{{std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)}},
            true},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::overwrite)
                .appendLimit(1),
            std::vector<ReadingData>{{std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)}},
            true},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::overwrite)
                .appendLimit(0),
            std::vector<ReadingData>{{std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)}},
            true},
        ReportUpdatesReportParams{
            defaultParams()
                .reportUpdates(ReportUpdates::appendStopsWhenFull)
                .appendLimit(2u),
            std::vector<ReadingData>{{std::make_tuple("b"s, 17.1, 114u),
                                      std::make_tuple("bb"s, 42.0, 74u)}},
            false}));

TEST_P(TestReportWithReportUpdatesAndLimit,
       readingsAreUpdatedAfterIntervalExpires)
{
    sut = makeReport(ReportParams(GetParam().reportParams)
                         .reportingType(ReportingType::periodic)
                         .interval(std::chrono::hours(1000)));

    updateReportFourTimes();

    EXPECT_THAT(readings(), ElementsAreArray(GetParam().expectedReadings));
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"),
                Eq(GetParam().expectedEnabled));
}

TEST_P(TestReportWithReportUpdatesAndLimit,
       appendLimitIsRespectedAfterChangingToPeriodic)
{
    sut = makeReport(ReportParams(GetParam().reportParams)
                         .appendLimit(GetParam().expectedReadings.size())
                         .reportingType(ReportingType::onRequest)
                         .interval(std::chrono::hours(0)));

    changeReport(ReportingType::periodic, std::chrono::hours(1000));
    updateReportFourTimes();

    EXPECT_THAT(readings(), ElementsAreArray(GetParam().expectedReadings));
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"),
                Eq(GetParam().expectedEnabled));
}

TEST_P(TestReportWithReportUpdatesAndLimit,
       appendLimitIsIgnoredAfterChangingToOnRequest)
{
    sut = makeReport(ReportParams(GetParam().reportParams)
                         .reportingType(ReportingType::periodic)
                         .interval(std::chrono::hours(1000)));

    changeReport(ReportingType::onRequest, Milliseconds{0});
    updateReportFourTimes();

    EXPECT_THAT(readings(), SizeIs(2u));
    EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"), Eq(true));
}

class TestReportInitialization : public TestReport
{
  public:
    void SetUp() override
    {
        initMetricMocks(defaultParams().metricParameters());
    }

    void monitorProc(sdbusplus::message_t& msg)
    {
        std::string iface;
        std::vector<std::pair<std::string, std::variant<Readings>>>
            changed_properties;
        std::vector<std::string> invalidated_properties;

        msg.read(iface, changed_properties, invalidated_properties);

        if (iface == Report::reportIfaceName)
        {
            for (const auto& [name, value] : changed_properties)
            {
                if (name == "Readings")
                {
                    readingsUpdated.Call();
                }
            }
        }
    }

    void makeMonitor()
    {
        monitor = std::make_unique<sdbusplus::bus::match_t>(
            *DbusEnvironment::getBus(),
            sdbusplus::bus::match::rules::propertiesChanged(
                sut->getPath(), Report::reportIfaceName),
            [this](auto& msg) { monitorProc(msg); });
    }

    std::unique_ptr<sdbusplus::bus::match_t> monitor;
    MockFunction<void()> readingsUpdated;
};

TEST_F(TestReportInitialization,
       registersForMetricUpdatesWhenOnChangeReportCreated)
{
    std::vector<const interfaces::MetricListener*> args;
    for (auto& metric : metricMocks)
    {
        EXPECT_CALL(*metric, registerForUpdates(_))
            .WillOnce(Invoke([&args](const interfaces::MetricListener& report) {
            args.emplace_back(&report);
        }));
        ;
    }

    sut = makeReport(defaultParams().reportingType(ReportingType::onChange));

    EXPECT_THAT(args, SizeIs(metricMocks.size()));
    for (const auto* reportPtr : args)
    {
        EXPECT_THAT(reportPtr, Eq(sut.get()));
    }
}

TEST_F(TestReportInitialization,
       deregistersForMetricUpdatesWhenOnChangeReportDestroyed)
{
    sut = makeReport(defaultParams().reportingType(ReportingType::onChange));

    for (auto& metric : metricMocks)
    {
        EXPECT_CALL(*metric,
                    unregisterFromUpdates(Ref(
                        static_cast<interfaces::MetricListener&>(*sut.get()))));
    }

    sut = nullptr;
}

TEST_F(TestReportInitialization,
       metricsAreInitializedWhenEnabledReportConstructed)
{
    for (auto& metric : metricMocks)
    {
        EXPECT_CALL(*metric, initialize());
    }
    sut = makeReport(defaultParams().enabled(true));
}

TEST_F(TestReportInitialization,
       metricsAreNotInitializedWhenDisabledReportConstructed)
{
    for (auto& metric : metricMocks)
    {
        EXPECT_CALL(*metric, initialize()).Times(0);
    }
    sut = makeReport(defaultParams().enabled(false));
}

TEST_F(TestReportInitialization,
       emitReadingsUpdateIsTrueReadingsPropertiesChangedSingalEmits)
{
    EXPECT_CALL(readingsUpdated, Call())
        .WillOnce(
            InvokeWithoutArgs(DbusEnvironment::setPromise("readingsUpdated")));

    const auto elapsed = DbusEnvironment::measureTime([this] {
        sut = makeReport(defaultParams()
                             .reportingType(ReportingType::periodic)
                             .reportActions({ReportAction::emitsReadingsUpdate})
                             .interval(ReportManager::minInterval));
        makeMonitor();
        EXPECT_TRUE(DbusEnvironment::waitForFuture("readingsUpdated"));
    });

    EXPECT_THAT(elapsed, AllOf(Ge(ReportManager::minInterval),
                               Lt(ReportManager::minInterval * 2)));
}

TEST_F(TestReportInitialization,
       emitReadingsUpdateIsFalseReadingsPropertiesChangesSigalDoesNotEmits)
{
    EXPECT_CALL(readingsUpdated, Call()).Times(0);

    sut = makeReport(defaultParams()
                         .reportingType(ReportingType::periodic)
                         .reportActions({})
                         .interval(Milliseconds(1000)));
    makeMonitor();
    DbusEnvironment::sleepFor(defaultParams().interval() * 2);
}

TEST_F(TestReportInitialization, triggerIdsPropertyIsInitialzed)
{
    for (const auto& triggerId : {"trigger1", "trigger2"})
    {
        messanger.on_receive<messages::CollectTriggerIdReq>(
            [this, triggerId](const auto& msg) {
            messanger.send(messages::CollectTriggerIdResp{triggerId});
        });
    }

    sut = makeReport(ReportParams());

    EXPECT_THAT(
        getProperty<std::vector<object_path>>(sut->getPath(), "Triggers"),
        UnorderedElementsAre(utils::constants::triggerDirPath / "trigger1",
                             utils::constants::triggerDirPath / "trigger2"));
}

TEST_F(TestReportInitialization,
       metricValuesAreNotStoredForReportUpdatesDifferentThanAppendStopsWhenFull)
{
    sut = makeReport(ReportParams()
                         .reportingType(ReportingType::periodic)
                         .interval(1h)
                         .reportUpdates(ReportUpdates::appendWrapsWhenFull)
                         .readings(Readings{{}, {{}}}));

    ASSERT_THAT(storedConfiguration.find("MetricValues"),
                Eq(storedConfiguration.end()));
}

TEST_F(TestReportInitialization, metricValuesAreNotStoredForOnRequestReport)
{
    sut = makeReport(ReportParams()
                         .reportingType(ReportingType::onRequest)
                         .reportUpdates(ReportUpdates::appendStopsWhenFull)
                         .readings(Readings{{}, {{}}}));

    ASSERT_THAT(storedConfiguration.find("MetricValues"),
                Eq(storedConfiguration.end()));
}

TEST_F(TestReportInitialization,
       metricValuesAreStoredForNonOnRequestReportWithAppendStopsWhenFull)
{
    const auto readings = Readings{{}, {{}}};

    sut = makeReport(ReportParams()
                         .reportingType(ReportingType::periodic)
                         .interval(1h)
                         .reportUpdates(ReportUpdates::appendStopsWhenFull)
                         .readings(readings));

    ASSERT_THAT(storedConfiguration.at("MetricValues").get<LabeledReadings>(),
                Eq(utils::toLabeledReadings(readings)));
}

class TestReportInitializationOnChangeReport : public TestReportInitialization
{
  public:
    void SetUp() override
    {
        initMetricMocks(params.metricParameters());
    }

    ReportParams params = defaultOnChangeParams();
};

TEST_F(TestReportInitializationOnChangeReport,
       doesntUpdateReadingsWhenNotRequired)
{
    EXPECT_CALL(*metricMocks[0], updateReadings(_)).Times(0);

    ON_CALL(*metricMocks[0], isTimerRequired()).WillByDefault(Return(false));

    sut = makeReport(params);

    DbusEnvironment::sleepFor(500ms);
}

TEST_F(TestReportInitializationOnChangeReport, updatesReadingsWhenRequired)
{
    EXPECT_CALL(*metricMocks[0], updateReadings(_))
        .WillOnce(Return())
        .WillOnce(
            InvokeWithoutArgs(DbusEnvironment::setPromise("readingsUpdated")))
        .WillRepeatedly(Return());

    ON_CALL(*metricMocks[0], isTimerRequired()).WillByDefault(Return(true));

    sut = makeReport(params);

    DbusEnvironment::waitForFuture("readingsUpdated");
}