#include "config.h"

#include "health_metric_config.hpp"

#include <nlohmann/json.hpp>
#include <phosphor-logging/lg2.hpp>

#include <cmath>
#include <fstream>
#include <ranges>
#include <unordered_map>
#include <unordered_set>
#include <utility>

PHOSPHOR_LOG2_USING;

namespace phosphor::health::metric
{
namespace config
{

using json = nlohmann::json;

// Default health metric config
extern json defaultHealthMetricConfig;

// Valid thresholds from config
static const auto validThresholdTypesWithBound =
    std::unordered_set<std::string>{"Critical_Lower", "Critical_Upper",
                                    "Warning_Lower", "Warning_Upper"};

static const auto validThresholdBounds =
    std::unordered_map<std::string, ThresholdIntf::Bound>{
        {"Lower", ThresholdIntf::Bound::Lower},
        {"Upper", ThresholdIntf::Bound::Upper}};

static const auto validThresholdTypes =
    std::unordered_map<std::string, ThresholdIntf::Type>{
        {"HardShutdown", ThresholdIntf::Type::HardShutdown},
        {"SoftShutdown", ThresholdIntf::Type::SoftShutdown},
        {"PerformanceLoss", ThresholdIntf::Type::PerformanceLoss},
        {"Critical", ThresholdIntf::Type::Critical},
        {"Warning", ThresholdIntf::Type::Warning}};

// Valid metrics from config
static const auto validTypes =
    std::unordered_map<std::string, Type>{{"CPU", Type::cpu},
                                          {"Memory", Type::memory},
                                          {"Storage", Type::storage},
                                          {"Inode", Type::inode}};

// Valid submetrics from config
static const auto validSubTypes = std::unordered_map<std::string, SubType>{
    {"CPU", SubType::cpuTotal},
    {"CPU_User", SubType::cpuUser},
    {"CPU_Kernel", SubType::cpuKernel},
    {"Memory", SubType::memoryTotal},
    {"Memory_Free", SubType::memoryFree},
    {"Memory_Available", SubType::memoryAvailable},
    {"Memory_Shared", SubType::memoryShared},
    {"Memory_Buffered_And_Cached", SubType::memoryBufferedAndCached},
    {"Storage_RW", SubType::NA},
    {"Storage_TMP", SubType::NA}};

/** Deserialize a Threshold from JSON. */
void from_json(const json& j, Threshold& self)
{
    self.value = j.value("Value", 100.0);
    self.log = j.value("Log", false);
    self.target = j.value("Target", Threshold::defaults::target);
}

/** Deserialize a HealthMetric from JSON. */
void from_json(const json& j, HealthMetric& self)
{
    self.windowSize = j.value("Window_size",
                              HealthMetric::defaults::windowSize);
    self.hysteresis = j.value("Hysteresis", HealthMetric::defaults::hysteresis);
    // Path is only valid for storage
    self.path = j.value("Path", "");

    auto thresholds = j.find("Threshold");
    if (thresholds == j.end())
    {
        return;
    }

    for (auto& [key, value] : thresholds->items())
    {
        if (!validThresholdTypesWithBound.contains(key))
        {
            warning("Invalid ThresholdType: {TYPE}", "TYPE", key);
            continue;
        }

        auto config = value.template get<Threshold>();
        if (!std::isfinite(config.value))
        {
            throw std::invalid_argument("Invalid threshold value");
        }

        static constexpr auto keyDelimiter = "_";
        std::string typeStr = key.substr(0, key.find_first_of(keyDelimiter));
        std::string boundStr = key.substr(key.find_last_of(keyDelimiter) + 1,
                                          key.length());

        self.thresholds.emplace(
            std::make_tuple(validThresholdTypes.at(typeStr),
                            validThresholdBounds.at(boundStr)),
            config);
    }
}

json parseConfigFile(std::string configFile)
{
    std::ifstream jsonFile(configFile);
    if (!jsonFile.is_open())
    {
        info("config JSON file not found: {PATH}", "PATH", configFile);
        return {};
    }

    try
    {
        return json::parse(jsonFile, nullptr, true);
    }
    catch (const json::parse_error& e)
    {
        error("Failed to parse JSON config file {PATH}: {ERROR}", "PATH",
              configFile, "ERROR", e);
    }

    return {};
}

void printConfig(HealthMetric::map_t& configs)
{
    for (auto& [type, configList] : configs)
    {
        for (auto& config : configList)
        {
            debug(
                "TYPE={TYPE}, NAME={NAME} SUBTYPE={SUBTYPE} PATH={PATH}, WSIZE={WSIZE}, HYSTERESIS={HYSTERESIS}",
                "TYPE", type, "NAME", config.name, "SUBTYPE", config.subType,
                "PATH", config.path, "WSIZE", config.windowSize, "HYSTERESIS",
                config.hysteresis);

            for (auto& [key, threshold] : config.thresholds)
            {
                debug(
                    "THRESHOLD TYPE={TYPE} THRESHOLD BOUND={BOUND} VALUE={VALUE} LOG={LOG} TARGET={TARGET}",
                    "TYPE", get<ThresholdIntf::Type>(key), "BOUND",
                    get<ThresholdIntf::Bound>(key), "VALUE", threshold.value,
                    "LOG", threshold.log, "TARGET", threshold.target);
            }
        }
    }
}

auto getHealthMetricConfigs() -> HealthMetric::map_t
{
    json mergedConfig(defaultHealthMetricConfig);

    if (auto platformConfig = parseConfigFile(HEALTH_CONFIG_FILE);
        !platformConfig.empty())
    {
        mergedConfig.merge_patch(platformConfig);
    }

    HealthMetric::map_t configs = {};
    for (auto& [name, metric] : mergedConfig.items())
    {
        static constexpr auto nameDelimiter = "_";
        std::string typeStr = name.substr(0, name.find_first_of(nameDelimiter));

        auto type = validTypes.find(typeStr);
        if (type == validTypes.end())
        {
            warning("Invalid metric type: {TYPE}", "TYPE", typeStr);
            continue;
        }

        auto config = metric.template get<HealthMetric>();
        config.name = name;

        auto subType = validSubTypes.find(name);
        config.subType = (subType != validSubTypes.end() ? subType->second
                                                         : SubType::NA);

        configs[type->second].emplace_back(std::move(config));
    }
    printConfig(configs);
    return configs;
}

json defaultHealthMetricConfig = R"({
    "CPU": {
        "Window_size": 120,
        "Hysteresis": 1.0,
        "Threshold": {
            "Critical_Upper": {
                "Value": 90.0,
                "Log": true,
                "Target": ""
            },
            "Warning_Upper": {
                "Value": 80.0,
                "Log": false,
                "Target": ""
            }
        }
    },
    "CPU_User": {
        "Window_size": 120,
        "Hysteresis": 1.0
    },
    "CPU_Kernel": {
        "Window_size": 120,
        "Hysteresis": 1.0
    },
    "Memory": {
        "Window_size": 120,
        "Hysteresis": 1.0
    },
    "Memory_Available": {
        "Window_size": 120,
        "Hysteresis": 1.0,
        "Threshold": {
            "Critical_Lower": {
                "Value": 15.0,
                "Log": true,
                "Target": ""
            }
        }
    },
    "Memory_Free": {
        "Window_size": 120,
        "Hysteresis": 1.0
    },
    "Memory_Shared": {
        "Window_size": 120,
        "Hysteresis": 1.0,
        "Threshold": {
            "Critical_Upper": {
                "Value": 85.0,
                "Log": true,
                "Target": ""
            }
        }
    },
    "Memory_Buffered_And_Cached": {
        "Window_size": 120,
        "Hysteresis": 1.0
    },
    "Storage_RW": {
        "Path": "/run/initramfs/rw",
        "Window_size": 120,
        "Hysteresis": 1.0,
        "Threshold": {
            "Critical_Lower": {
                "Value": 15.0,
                "Log": true,
                "Target": ""
            }
        }
    },
    "Storage_TMP": {
        "Path": "/tmp",
        "Window_size": 120,
        "Hysteresis": 1.0,
        "Threshold": {
            "Critical_Lower": {
                "Value": 15.0,
                "Log": true,
                "Target": ""
            }
        }
    }
})"_json;

} // namespace config

namespace details
{
auto reverse_map_search(const auto& m, auto v)
{
    if (auto match = std::ranges::find_if(
            m, [=](const auto& p) { return p.second == v; });
        match != std::end(m))
    {
        return match->first;
    }
    return std::format("Enum({})", std::to_underlying(v));
}
} // namespace details

// to_string specialization for Type.
auto to_string(Type t) -> std::string
{
    return details::reverse_map_search(config::validTypes, t);
}

// to_string specializaiton for SubType.
auto to_string(SubType t) -> std::string
{
    return details::reverse_map_search(config::validSubTypes, t);
}

} // namespace phosphor::health::metric