#include "event_parser.hpp"

#include <filesystem>
#include <fstream>
#include <iostream>
#include <set>
#include <xyz/openbmc_project/Common/error.hpp>

namespace pldm::responder::events
{

namespace fs = std::filesystem;
using InternalFailure =
    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;

const Json emptyJson{};
const std::vector<Json> emptyJsonList{};
const std::vector<std::string> emptyStringVec{};

constexpr auto eventStateSensorJson = "event_state_sensor.json";

const std::set<std::string_view> supportedDbusPropertyTypes = {
    "bool",     "uint8_t", "int16_t",  "uint16_t", "int32_t",
    "uint32_t", "int64_t", "uint64_t", "double",   "string"};

StateSensorHandler::StateSensorHandler(const std::string& dirPath)
{
    fs::path dir(dirPath);
    if (!fs::exists(dir) || fs::is_empty(dir))
    {
        std::cerr << "Event config directory does not exist or empty, DIR="
                  << dirPath << "\n";
        return;
    }

    fs::path filePath = dir / eventStateSensorJson;
    if (!fs::exists(filePath))
    {
        std::cerr << "Event state sensor JSON does not exist, PATH=" << filePath
                  << "\n";
        throw InternalFailure();
    }

    std::ifstream jsonFile(filePath);

    auto data = Json::parse(jsonFile, nullptr, false);
    if (data.is_discarded())
    {
        std::cerr << "Parsing Event state sensor JSON file failed, FILE="
                  << filePath;
        throw InternalFailure();
    }

    auto entries = data.value("entries", emptyJsonList);
    for (const auto& entry : entries)
    {
        StateSensorEntry stateSensorEntry{};
        stateSensorEntry.containerId =
            static_cast<uint16_t>(entry.value("containerID", 0));
        stateSensorEntry.entityType =
            static_cast<uint16_t>(entry.value("entityType", 0));
        stateSensorEntry.entityInstance =
            static_cast<uint16_t>(entry.value("entityInstance", 0));
        stateSensorEntry.sensorOffset =
            static_cast<uint8_t>(entry.value("sensorOffset", 0));

        pldm::utils::DBusMapping dbusInfo{};

        auto dbus = entry.value("dbus", emptyJson);
        dbusInfo.objectPath = dbus.value("object_path", "");
        dbusInfo.interface = dbus.value("interface", "");
        dbusInfo.propertyName = dbus.value("property_name", "");
        dbusInfo.propertyType = dbus.value("property_type", "");
        if (dbusInfo.objectPath.empty() || dbusInfo.interface.empty() ||
            dbusInfo.propertyName.empty() ||
            (supportedDbusPropertyTypes.find(dbusInfo.propertyType) ==
             supportedDbusPropertyTypes.end()))
        {
            std::cerr << "Invalid dbus config,"
                      << " OBJPATH=" << dbusInfo.objectPath << " INTERFACE="
                      << dbusInfo.interface << " PROPERTY_NAME="
                      << dbusInfo.propertyName
                      << " PROPERTY_TYPE=" << dbusInfo.propertyType << "\n";
            continue;
        }

        auto eventStates = entry.value("event_states", emptyJsonList);
        auto propertyValues = dbus.value("property_values", emptyJsonList);
        if ((eventStates.size() == 0) || (propertyValues.size() == 0) ||
            (eventStates.size() != propertyValues.size()))
        {
            std::cerr << "Invalid event state JSON config,"
                      << " EVENT_STATE_SIZE=" << eventStates.size()
                      << " PROPERTY_VALUE_SIZE=" << propertyValues.size()
                      << "\n";
            continue;
        }

        auto eventStateMap = mapStateToDBusVal(eventStates, propertyValues,
                                               dbusInfo.propertyType);
        eventMap.emplace(
            stateSensorEntry,
            std::make_tuple(std::move(dbusInfo), std::move(eventStateMap)));
    }
}

StateToDBusValue StateSensorHandler::mapStateToDBusVal(
    const Json& eventStates, const Json& propertyValues, std::string_view type)
{
    StateToDBusValue eventStateMap{};
    auto stateIt = eventStates.begin();
    auto propIt = propertyValues.begin();

    for (; stateIt != eventStates.end(); ++stateIt, ++propIt)
    {
        auto propValue = utils::jsonEntryToDbusVal(type, propIt.value());
        eventStateMap.emplace((*stateIt).get<uint8_t>(), std::move(propValue));
    }

    return eventStateMap;
}

int StateSensorHandler::eventAction(const StateSensorEntry& entry,
                                    pdr::EventState state)
{
    try
    {
        const auto& [dbusMapping, eventStateMap] = eventMap.at(entry);
        utils::PropertyValue propValue{};
        try
        {
            propValue = eventStateMap.at(state);
        }
        catch (const std::out_of_range& e)
        {
            std::cerr << "Invalid event state" << static_cast<unsigned>(state)
                      << '\n';
            return PLDM_ERROR_INVALID_DATA;
        }

        try
        {
            pldm::utils::DBusHandler().setDbusProperty(dbusMapping, propValue);
        }
        catch (const std::exception& e)
        {
            std::cerr << "Error setting property, ERROR=" << e.what()
                      << " PROPERTY=" << dbusMapping.propertyName
                      << " INTERFACE=" << dbusMapping.interface << " PATH="
                      << dbusMapping.objectPath << "\n";
            return PLDM_ERROR;
        }
    }
    catch (const std::out_of_range& e)
    {
        // There is no BMC action for this PLDM event
        return PLDM_SUCCESS;
    }
    return PLDM_SUCCESS;
}

} // namespace pldm::responder::events