#include "fru_parser.hpp"

#include <nlohmann/json.hpp>
#include <phosphor-logging/lg2.hpp>
#include <xyz/openbmc_project/Common/error.hpp>

#include <filesystem>
#include <fstream>
#include <iostream>

PHOSPHOR_LOG2_USING;

using namespace pldm::responder::dbus;

namespace pldm
{
namespace responder
{
namespace fru_parser
{
using Json = nlohmann::json;
using InternalFailure =
    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;

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

FruParser::FruParser(const std::string& dirPath,
                     const fs::path& fruMasterJsonPath)
{
    if (fs::exists(fruMasterJsonPath))
    {
        setupDefaultDBusLookup(fruMasterJsonPath);
    }
    setupDefaultFruRecordMap();

    fs::path dir(dirPath);
    if (fs::exists(dir) && !fs::is_empty(dir))
    {
        setupFruRecordMap(dirPath);
    }
}

void FruParser::setupDefaultDBusLookup(const fs::path& masterJsonPath)
{
    constexpr auto service = "xyz.openbmc_project.Inventory.Manager";
    constexpr auto rootPath = "/xyz/openbmc_project/inventory";
    std::ifstream jsonFile(masterJsonPath);
    auto data = Json::parse(jsonFile, nullptr, false);
    if (data.is_discarded())
    {
        error(
            "Parsing FRU Dbus Lookup Map config file failed, FILE={JSON_PATH}",
            "JSON_PATH", masterJsonPath.c_str());
        std::abort();
    }
    std::map<Interface, EntityType> defIntfToEntityType;
    auto dbusMap = data.value("FruDBusLookupMap", emptyJson);
    for (const auto& element : dbusMap.items())

    {
        try
        {
            defIntfToEntityType[static_cast<Interface>(element.key())] =
                static_cast<EntityType>(element.value());
        }
        catch (const std::exception& e)
        {
            error("FRU DBus lookup map format error");
            throw InternalFailure();
        }
    }

    Interfaces interfaces{};
    for (auto [intf, entityType] : defIntfToEntityType)
    {
        intfToEntityType[intf] = entityType;
        interfaces.emplace(intf);
    }

    lookupInfo.emplace(service, rootPath, std::move(interfaces));
}

void FruParser::setupDefaultFruRecordMap()
{
    const FruRecordInfo generalRecordInfo = {
        1, // generalRecordType
        1, // encodingTypeASCII
        {
            // DSP0257 Table 5 General FRU Record Field Type Definitions
            {"xyz.openbmc_project.Inventory.Decorator.Asset", "Model", "string",
             2},
            {"xyz.openbmc_project.Inventory.Decorator.Asset", "PartNumber",
             "string", 3},
            {"xyz.openbmc_project.Inventory.Decorator.Asset", "SerialNumber",
             "string", 4},
            {"xyz.openbmc_project.Inventory.Decorator.Asset", "Manufacturer",
             "string", 5},
            {"xyz.openbmc_project.Inventory.Item", "PrettyName", "string", 8},
            {"xyz.openbmc_project.Inventory.Decorator.AssetTag", "AssetTag",
             "string", 11},
            {"xyz.openbmc_project.Inventory.Decorator.Revision", "Version",
             "string", 10},
        }};

    for (auto [intf, entityType] : intfToEntityType)
    {
        recordMap[intf] = {generalRecordInfo};
    }
}

void FruParser::setupFruRecordMap(const std::string& dirPath)
{
    for (auto& file : fs::directory_iterator(dirPath))
    {
        auto fileName = file.path().filename().string();
        std::ifstream jsonFile(file.path());
        auto data = Json::parse(jsonFile, nullptr, false);
        if (data.is_discarded())
        {
            error("Parsing FRU config file failed, FILE={FILE_PATH}",
                  "FILE_PATH", file.path().c_str());
            throw InternalFailure();
        }

        try
        {
            auto record = data.value("record_details", emptyJson);
            auto recordType =
                static_cast<uint8_t>(record.value("fru_record_type", 0));
            auto encType =
                static_cast<uint8_t>(record.value("fru_encoding_type", 0));
            auto dbusIntfName = record.value("dbus_interface_name", "");
            auto entries = data.value("fru_fields", emptyJsonList);
            std::vector<FieldInfo> fieldInfo;

            for (const auto& entry : entries)
            {
                auto fieldType =
                    static_cast<uint8_t>(entry.value("fru_field_type", 0));
                auto dbus = entry.value("dbus", emptyJson);
                auto interface = dbus.value("interface", "");
                auto property = dbus.value("property_name", "");
                auto propType = dbus.value("property_type", "");
                fieldInfo.emplace_back(
                    std::make_tuple(std::move(interface), std::move(property),
                                    std::move(propType), std::move(fieldType)));
            }

            FruRecordInfo fruInfo;
            fruInfo =
                std::make_tuple(recordType, encType, std::move(fieldInfo));

            auto search = recordMap.find(dbusIntfName);

            // PLDM FRU can have multiple records for the same FRU like General
            // FRU record and multiple OEM FRU records. If the FRU item
            // interface name is already in the map, that indicates a record
            // info is already added for the FRU, so append the new record info
            // to the same data.
            if (search != recordMap.end())
            {
                search->second.emplace_back(std::move(fruInfo));
            }
            else
            {
                FruRecordInfos recordInfos{fruInfo};
                recordMap.emplace(dbusIntfName, recordInfos);
            }
        }
        catch (const std::exception& e)
        {
            continue;
        }
    }
}

} // namespace fru_parser

} // namespace responder

} // namespace pldm