// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest

#include <everest/database/exceptions.hpp>
#include <ocpp/common/utils.hpp>
#include <ocpp/v2/ctrlr_component_variables.hpp>
#include <ocpp/v2/device_model.hpp>
#include <ocpp/v2/device_model_storage_sqlite.hpp>
#include <ocpp/v2/utils.hpp>

namespace ocpp {

namespace v2 {

namespace {
/// \brief For AlignedDataInterval, SampledDataTxUpdatedInterval and SampledDataTxEndedInterval, zero is allowed
bool allow_zero(const Component& component, const Variable& variable) {
    ComponentVariable component_variable;
    component_variable.component = component;
    component_variable.variable = variable;
    return component_variable == ControllerComponentVariables::AlignedDataInterval or
           component_variable == ControllerComponentVariables::SampledDataTxUpdatedInterval or
           component_variable == ControllerComponentVariables::SampledDataTxEndedInterval;
}

bool allow_set_read_only_value(const Component& component, const Variable& variable,
                               const AttributeEnum attribute_enum) {
    if (attribute_enum != AttributeEnum::Actual) {
        return false;
    }

    return component == ControllerComponents::AuthCacheCtrlr or component == ControllerComponents::LocalAuthListCtrlr or
           component == ControllerComponents::OCPPCommCtrlr or component == ControllerComponents::SecurityCtrlr or
           variable == EvseComponentVariables::AvailabilityState or variable == EvseComponentVariables::Power or
           variable == ConnectorComponentVariables::AvailabilityState;
}

} // namespace

bool DeviceModel::component_criteria_match(const Component& component,
                                           const std::vector<ComponentCriterionEnum>& component_criteria) {
    if (component_criteria.empty()) {
        return false;
    }
    for (const auto& criteria : component_criteria) {
        Variable variable;
        variable.name = conversions::component_criterion_enum_to_string(criteria);

        const auto response = this->request_value<bool>(component, variable, AttributeEnum::Actual);
        auto value = response.value;
        if (response.status == GetVariableStatusEnum::Accepted and value.has_value() and value.value()) {
            return true;
        }
        // also send true if the component crietria isn't part of the component except "problem"
        if (!value.has_value() and (variable.name != "Problem")) {
            return true;
        }
    }
    return false;
}

namespace {
/// @brief Iterates over the given \p component_variables and filters them according to the requirement conditions.
/// @param component_variables
/// @param component_ current component
/// @param variable_ current variable
/// @return true if the component is found according to any of the requirement conditions.
bool component_variables_match(const std::vector<ComponentVariable>& component_variables,
                               const ocpp::v2::Component& component, const ocpp::v2::Variable& variable) {

    return std::find_if(
               component_variables.begin(), component_variables.end(), [component, variable](ComponentVariable v) {
                   return (component == v.component and !v.variable.has_value()) or // if component has no variable
                          (component == v.component and v.variable.has_value() and
                           variable == v.variable.value()) or // if component has variables
                          (component == v.component and v.variable.has_value() and
                           !v.variable.value().instance.has_value() and
                           variable.name == v.variable.value().name) or // if component has no variable instances
                          (!v.component.evse.has_value() and (component.name == v.component.name) and
                           (component.instance == v.component.instance) and (variable == v.variable)); // B08.FR.23
               }) != component_variables.end();
}
} // namespace

void DeviceModel::check_variable_has_value(const ComponentVariable& component_variable, const AttributeEnum attribute) {
    std::string value;
    if (not component_variable.variable.has_value()) {
        throw DeviceModelError("Attempted to check if a variale of component " +
                               component_variable.component.name.get() +
                               " has a value but did not provide the variable");
    }
    const auto& variable = component_variable.variable.value();
    const auto response = this->request_value_internal(component_variable.component, variable, attribute, value, true);

    if (response != GetVariableStatusEnum::Accepted) {
        throw DeviceModelError("Required variable " + variable.name.get() + " of component " +
                               component_variable.component.name.get() + " does not have a value in the device model");
    }
}

void DeviceModel::check_required_variable(const RequiredComponentVariable& required_variable,
                                          const std::vector<OcppProtocolVersion>& supported_versions) {
    if (supported_versions.empty()) {
        throw DeviceModelError("Could not find supported ocpp versions in the InternalCtrlr.");
    }

    bool required = false;
    for (auto& supported_version : supported_versions) {
        if (required_variable.required_for.count(supported_version) > 0) {
            required = true;
        }
    }

    // For the current supported ocpp protocol versions, this variable is not required. So skip further checks.
    if (!required) {
        return;
    }

    if (!required_variable.variable.has_value()) {
        throw DeviceModelError("Required variable does not exist.");
    }

    check_variable_has_value(required_variable);
}

void DeviceModel::check_required_variables() {
    const auto supported_versions = utils::get_ocpp_protocol_versions(
        this->get_value<std::string>(ControllerComponentVariables::SupportedOcppVersions));

    if (supported_versions.empty()) {
        throw DeviceModelError("Could not find supported OCPP versions in the InternalCtrlr.");
    }

    std::vector<std::string> missing_var_errors;

    // Check global required variables
    for (const auto& required_variable : required_variables) {
        try {
            check_required_variable(required_variable, supported_versions);
        } catch (const std::exception& e) {
            std::stringstream ss;
            ss << required_variable.component.name << "/"
               << (required_variable.variable.has_value() ? required_variable.variable.value().name : "<unnamed>")
               << ": " << e.what();
            missing_var_errors.push_back(ss.str());
        }
    }

    // Check controller-specific required variables (if controller is available)
    for (const auto& available_required : required_component_available_variables) {
        const std::optional<bool> available = this->get_optional_value<bool>(available_required.first);
        if (!available.value_or(false)) {
            continue;
        }

        for (const auto& required_variable : available_required.second) {
            try {
                check_required_variable(required_variable, supported_versions);
            } catch (const std::exception& e) {
                std::stringstream ss;
                ss << required_variable.component.name << "/"
                   << (required_variable.variable.has_value() ? required_variable.variable.value().name : "<unnamed>")
                   << ": " << e.what();
                missing_var_errors.push_back(ss.str());
            }
        }
    }

    // Throw collective error if any variables are missing
    if (!missing_var_errors.empty()) {
        std::ostringstream oss;
        oss << "Missing required variables:\n";
        for (const auto& err : missing_var_errors) {
            oss << " - " << err << "\n";
        }
        throw DeviceModelError(oss.str());
    }
}

namespace {
bool validate_value(const VariableCharacteristics& characteristics, const std::string& value, bool allow_zero) {
    switch (characteristics.dataType) {
    case DataEnum::string:
        if (characteristics.minLimit.has_value() and
            value.size() < convert_to_positive_size_t(characteristics.minLimit.value())) {
            return false;
        }
        if (characteristics.maxLimit.has_value() and
            value.size() > convert_to_positive_size_t(characteristics.maxLimit.value())) {
            return false;
        }
        return true;
    case DataEnum::decimal: {
        if (!is_decimal_number(value)) {
            return false;
        }
        const float f = std::stof(value);

        if (allow_zero and f == 0) {
            return true;
        }
        if (characteristics.minLimit.has_value() and f < characteristics.minLimit.value()) {
            return false;
        }
        if (characteristics.maxLimit.has_value() and f > characteristics.maxLimit.value()) {
            return false;
        }
        return true;
    }
    case DataEnum::integer: {
        if (!is_integer(value)) {
            return false;
        }

        const int i = std::stoi(value);

        if (allow_zero and i == 0) {
            return true;
        }
        if (characteristics.minLimit.has_value() and i < convert_to_positive_size_t(characteristics.minLimit.value())) {
            return false;
        }
        if (characteristics.maxLimit.has_value() and i > convert_to_positive_size_t(characteristics.maxLimit.value())) {
            return false;
        }
        return true;
    }
    case DataEnum::dateTime: {
        return is_rfc3339_datetime(value);
    }
    case DataEnum::boolean:
        return (value == "true" or value == "false");
    case DataEnum::OptionList: {
        // OptionList: The (Actual) Variable value must be a single value from the reported (CSV) enumeration list.
        if (!characteristics.valuesList.has_value()) {
            return true;
        }
        const auto values_list = ocpp::split_string(characteristics.valuesList.value().get(), ',');
        return std::find(values_list.begin(), values_list.end(), value) != values_list.end();
    }
    case DataEnum::SequenceList:
    case DataEnum::MemberList:
        // MemberList: The (Actual) Variable value may be an (unordered) (sub-)set of the reported (CSV) valid
        // values list. SequenceList: The (Actual) Variable value may be an ordered (priority, etc) (sub-)set of the
        // reported (CSV) valid values.
        {
            if (!characteristics.valuesList.has_value()) {
                return true;
            }
            const auto values_list = ocpp::split_string(characteristics.valuesList.value().get(), ',');
            const auto value_csv = ocpp::split_string(value, ',');
            for (const auto& v : value_csv) {
                if (std::find(values_list.begin(), values_list.end(), v) == values_list.end()) {
                    return false;
                }
            }
            return true;
        }
    }
    return false;
}

bool include_in_summary_inventory(const ComponentVariable& cv, const VariableAttribute& attribute) {
    if (cv == ControllerComponentVariables::ChargingStationAvailabilityState) {
        return true;
    }
    if (cv.component.name == "EVSE" and cv.variable == EvseComponentVariables::AvailabilityState) {
        return true;
    }
    if (cv.component.name == "Connector" and cv.variable == ConnectorComponentVariables::AvailabilityState) {
        return true;
    }
    if ((cv.variable == StandardizedVariables::Fallback or cv.variable == StandardizedVariables::Overload or
         cv.variable == StandardizedVariables::Problem or cv.variable == StandardizedVariables::Tripped) and
        attribute.value.value_or("") == "true") {
        return true;
    }
    return false;
}
} // namespace

GetVariableStatusEnum DeviceModel::get_variable(const Component& component_id, const Variable& variable_id,
                                                const AttributeEnum& attribute_enum, std::string& value,
                                                bool allow_write_only) const {
    return this->request_value_internal(component_id, variable_id, attribute_enum, value, allow_write_only);
}

GetVariableStatusEnum DeviceModel::request_value_internal(const Component& component_id, const Variable& variable_id,
                                                          const AttributeEnum& attribute_enum, std::string& value,
                                                          bool allow_write_only) const {
    const auto component_it = this->device_model_map.find(component_id);
    if (component_it == this->device_model_map.end()) {
        EVLOG_debug << "unknown component in " << component_id.name << "." << variable_id.name;
        return GetVariableStatusEnum::UnknownComponent;
    }

    const auto& component = component_it->second;
    const auto& variable_it = component.find(variable_id);

    if (variable_it == component.end()) {
        EVLOG_debug << "unknown variable in " << component_id.name << "." << variable_id.name;
        return GetVariableStatusEnum::UnknownVariable;
    }

    const auto attribute_opt = this->device_model->get_variable_attribute(component_id, variable_id, attribute_enum);

    if ((not attribute_opt) or (not attribute_opt->value)) {
        return GetVariableStatusEnum::NotSupportedAttributeType;
    }

    // only internal functions can access WriteOnly variables
    if (!allow_write_only and attribute_opt.value().mutability.has_value() and
        attribute_opt.value().mutability.value() == MutabilityEnum::WriteOnly) {
        return GetVariableStatusEnum::Rejected;
    }

    value = attribute_opt->value->get();
    return GetVariableStatusEnum::Accepted;
}

std::optional<MutabilityEnum> DeviceModel::get_mutability(const Component& component, const Variable& variable,
                                                          const AttributeEnum& attribute_enum) {
    const auto attribute = this->device_model->get_variable_attribute(component, variable, attribute_enum);
    if (!attribute.has_value()) {
        return std::nullopt;
    }

    return attribute.value().mutability;
}

SetVariableStatusEnum DeviceModel::set_value(const Component& component, const Variable& variable,
                                             const AttributeEnum& attribute_enum, const std::string& value,
                                             const std::string& source, bool allow_read_only) {

    if (this->device_model_map.find(component) == this->device_model_map.end()) {
        return SetVariableStatusEnum::UnknownComponent;
    }

    auto variable_map = this->device_model_map[component];

    if (variable_map.find(variable) == variable_map.end()) {
        return SetVariableStatusEnum::UnknownVariable;
    }

    const auto& characteristics = variable_map[variable].characteristics;
    try {
        if (!validate_value(characteristics, value, allow_zero(component, variable))) {
            return SetVariableStatusEnum::Rejected;
        }
    } catch (const std::exception& e) {
        EVLOG_warning << "Could not validate value: " << value << " for component: " << component
                      << " and variable: " << variable;
        return SetVariableStatusEnum::Rejected;
    }

    const auto attribute = this->device_model->get_variable_attribute(component, variable, attribute_enum);

    if (!attribute.has_value()) {
        return SetVariableStatusEnum::NotSupportedAttributeType;
    }

    // If allow_read_only is false, don't allow read only
    if (!attribute.value().mutability.has_value() or
        ((attribute.value().mutability.value() == MutabilityEnum::ReadOnly) and !allow_read_only)) {
        return SetVariableStatusEnum::Rejected;
    }

    const auto result =
        this->device_model->set_variable_attribute_value(component, variable, attribute_enum, value, source);
    const auto success = (result == SetVariableStatusEnum::Accepted);

    // Only trigger for actual values
    if ((attribute_enum == AttributeEnum::Actual) && success && !variable_listener.empty()) {
        const auto& monitors = variable_map[variable].monitors;

        // If we had a variable value change, trigger the listener
        static const std::string EMPTY_VALUE{};

        const std::string& value_previous = attribute.value().value.value_or(EMPTY_VALUE);
        const std::string& value_current = value;

        if (value_previous != value_current) {
            for (const auto& listener : variable_listener) {
                listener(monitors, component, variable, characteristics, attribute.value(), value_previous,
                         value_current);
            }
        }
    }

    return result;
};

DeviceModel::DeviceModel(std::unique_ptr<DeviceModelStorageInterface> device_model_storage_interface) :
    device_model{std::move(device_model_storage_interface)} {
    this->device_model_map = this->device_model->get_device_model();
}

bool DeviceModel::create_network_configuration_slot_from_default_schema(std::int32_t new_slot) {
    if (this->device_model == nullptr) {
        return false;
    }
    if (!this->device_model->create_network_configuration_slot_from_default_schema(new_slot)) {
        return false;
    }
    try {
        // Reload the cached map so the freshly created component is visible to subsequent
        // get_variable / set_value calls; the cache is built once at construction.
        this->device_model_map = this->device_model->get_device_model();
    } catch (const std::exception& e) {
        EVLOG_error << "DeviceModel::create_network_configuration_slot_from_default_schema: storage "
                       "committed slot "
                    << new_slot << " but in-memory device-model reload failed (" << e.what()
                    << "); restart required to pick up the new component";
        throw;
    }
    return true;
}

SetVariableStatusEnum DeviceModel::set_read_only_value(const Component& component, const Variable& variable,
                                                       const AttributeEnum& attribute_enum, const std::string& value,
                                                       const std::string& source) {

    if (allow_set_read_only_value(component, variable, attribute_enum)) {
        return this->set_value(component, variable, attribute_enum, value, source, true);
    }
    throw std::invalid_argument("Not allowed to set read only value for component " + component.name.get() +
                                " and variable " + variable.name.get());
}

SetVariableStatusEnum DeviceModel::clear_value(const Component& component, const Variable& variable,
                                               const AttributeEnum& attribute_enum, const std::string& source) {
    if (this->device_model_map.find(component) == this->device_model_map.end()) {
        return SetVariableStatusEnum::UnknownComponent;
    }
    auto& variable_map = this->device_model_map[component];
    if (variable_map.find(variable) == variable_map.end()) {
        return SetVariableStatusEnum::UnknownVariable;
    }
    const auto attribute = this->device_model->get_variable_attribute(component, variable, attribute_enum);
    if (!attribute.has_value()) {
        return SetVariableStatusEnum::NotSupportedAttributeType;
    }
    // Bypass validate_value: empty-string clears can violate length/range characteristics
    // by design (the cleared row is the sentinel for "no value").
    const std::string empty;
    const auto result =
        this->device_model->set_variable_attribute_value(component, variable, attribute_enum, empty, source);

    // Fire variable_listener for value change observers, mirroring set_value's behavior.
    if ((attribute_enum == AttributeEnum::Actual) && (result == SetVariableStatusEnum::Accepted) &&
        !variable_listener.empty()) {
        const auto& monitors = variable_map[variable].monitors;
        static const std::string EMPTY_VALUE{};
        const std::string& value_previous = attribute.value().value.value_or(EMPTY_VALUE);
        if (value_previous != empty) {
            for (const auto& listener : variable_listener) {
                listener(monitors, component, variable, variable_map[variable].characteristics, attribute.value(),
                         value_previous, empty);
            }
        }
    }
    return result;
}

std::optional<VariableMetaData> DeviceModel::get_variable_meta_data(const Component& component,
                                                                    const Variable& variable) {
    if ((this->device_model_map.count(component) != 0) and
        (this->device_model_map.at(component).count(variable) != 0)) {
        return this->device_model_map.at(component).at(variable);
    }
    return std::nullopt;
}

std::vector<ReportData> DeviceModel::get_base_report_data(const ReportBaseEnum& report_base) {
    std::vector<ReportData> report_data_vec;

    for (const auto& [component, variable_map] : this->device_model_map) {
        for (const auto& [variable, variable_meta_data] : variable_map) {

            ReportData report_data;
            report_data.component = component;
            report_data.variable = variable;

            ComponentVariable cv;
            cv.component = component;
            cv.variable = variable;

            // request the variable attribute from the device model
            const auto variable_attributes = this->device_model->get_variable_attributes(component, variable);

            // iterate over possibly (Actual, Target, MinSet, MaxSet)
            for (const auto& variable_attribute : variable_attributes) {
                if (report_base == ReportBaseEnum::FullInventory or
                    (report_base == ReportBaseEnum::ConfigurationInventory and
                     (variable_attribute.mutability == MutabilityEnum::ReadWrite or
                      variable_attribute.mutability == MutabilityEnum::WriteOnly))) {
                    report_data.variableAttribute.push_back(variable_attribute);
                    // scrub WriteOnly value from report
                    if (variable_attribute.mutability == MutabilityEnum::WriteOnly) {
                        report_data.variableAttribute.back().value.reset();
                    }
                    report_data.variableCharacteristics = variable_map.at(variable).characteristics;
                } else if (report_base == ReportBaseEnum::SummaryInventory) {
                    if (include_in_summary_inventory(cv, variable_attribute)) {
                        report_data.variableAttribute.push_back(variable_attribute);
                    }
                }
            }
            if (!report_data.variableAttribute.empty()) {
                report_data_vec.push_back(report_data);
            }
        }
    }
    return report_data_vec;
}

std::vector<ReportData>
DeviceModel::get_custom_report_data(const std::optional<std::vector<ComponentVariable>>& component_variables,
                                    const std::optional<std::vector<ComponentCriterionEnum>>& component_criteria) {
    std::vector<ReportData> report_data_vec;

    for (const auto& [component, variable_map] : this->device_model_map) {
        if (!component_criteria.has_value() or component_criteria_match(component, component_criteria.value())) {

            for (const auto& [variable, variable_meta_data] : variable_map) {
                if (!component_variables.has_value() or
                    component_variables_match(component_variables.value(), component, variable)) {
                    ReportData report_data;
                    report_data.component = component;
                    report_data.variable = variable;

                    //  request the variable attribute from the device model
                    const auto variable_attributes = this->device_model->get_variable_attributes(component, variable);

                    for (const auto& variable_attribute : variable_attributes) {
                        report_data.variableAttribute.push_back(variable_attribute);
                        report_data.variableCharacteristics = variable_map.at(variable).characteristics;
                    }

                    if (!report_data.variableAttribute.empty()) {
                        report_data_vec.push_back(report_data);
                    }
                }
            }
        }
    }
    return report_data_vec;
}

void DeviceModel::check_integrity(const std::map<std::int32_t, std::int32_t>& evse_connector_structure) {
    EVLOG_debug << "Checking integrity of device model in storage";
    try {
        this->check_required_variables();
        this->device_model->check_integrity();

        std::int32_t nr_evse_components = 0;
        std::map<std::int32_t, std::int32_t> evse_id_nr_connector_components;

        for (const auto& [component, variable_map] : this->device_model_map) {
            if (component.name == "EVSE") {
                nr_evse_components++;
            } else if (component.name == "Connector") {
                if (not component.evse.has_value()) {
                    throw DeviceModelError("EVSE component did not contain evse member");
                }
                const auto& evse = component.evse.value();
                if (evse_id_nr_connector_components.count(evse.id) != 0) {
                    evse_id_nr_connector_components[evse.id] += 1;
                } else {
                    evse_id_nr_connector_components[evse.id] = 1;
                }
            }
        }

        // check if number of EVSE in the device model matches the configured number
        if (nr_evse_components != evse_connector_structure.size()) {
            throw DeviceModelError("Number of EVSE configured in device model is incompatible with number of "
                                   "configured EVSEs of the ChargePoint");
        }

        for (const auto [evse_id, nr_of_connectors] : evse_connector_structure) {
            // check if number of Cpnnectors for this EVSE in the device model matches the configured number
            if (evse_id_nr_connector_components[evse_id] != nr_of_connectors) {
                throw DeviceModelError("Number of Connectors configured in device model is incompatible with number "
                                       "of configured Connectors of the ChargePoint");
            }

            // check if all relevant EVSE and Connector components can be found
            EVSE evse;
            evse.id = evse_id;
            Component evse_component;
            evse_component.name = "EVSE";
            evse_component.evse = evse;
            if (this->device_model_map.count(evse_component) == 0) {
                throw DeviceModelError("Could not find required EVSE component in device model");
            }

            for (const auto& required_variable : required_evse_variables) {
                const auto& variable = EvseComponentVariables::get_component_variable(evse_id, required_variable);
                check_variable_has_value(variable);
            }

            const auto& variable =
                EvseComponentVariables::get_component_variable(evse_id, EvseComponentVariables::Power);
            std::map<Variable, VariableMetaData>& v = device_model_map[evse_component];
            if (v.count(EvseComponentVariables::Power) == 0) {
                throw DeviceModelError("Could not find required 'Power' variable in EVSE component in device model");
            }

            if (!v[EvseComponentVariables::Power].characteristics.maxLimit.has_value()) {
                throw DeviceModelError("maxLimit of 'Power' not set");
            }

            Component v2x_component;
            v2x_component.name = "V2XChargingCtrlr";
            v2x_component.evse = evse;
            if ((this->device_model_map.count(v2x_component) != 0) and
                std::any_of(evse_connector_structure.begin(), evse_connector_structure.end(),
                            [this](const auto& entry) {
                                const auto& [evse, connectors] = entry;
                                return get_optional_value<bool>(V2xComponentVariables::get_component_variable(
                                                                    evse, V2xComponentVariables::Available))
                                    .value_or(false);
                            })) {
                for (const auto& required_variable : required_v2x_variables) {
                    const auto& variable = V2xComponentVariables::get_component_variable(evse_id, required_variable);
                    check_variable_has_value(variable);
                }
            }

            for (size_t connector_id = 1; connector_id <= nr_of_connectors; connector_id++) {
                evse_component.name = "Connector";
                evse_component.evse.value().connectorId = connector_id;
                if (this->device_model_map.count(evse_component) == 0) {
                    throw DeviceModelError("Could not find required Connector component in device model");
                }

                for (const auto& required_variable : required_connector_variables) {
                    const auto& variable = ConnectorComponentVariables::get_component_variable(
                        evse_id, clamp_to<std::int32_t>(connector_id), required_variable);
                    check_variable_has_value(variable);
                }
            }
        }
    } catch (const DeviceModelError& e) {
        EVLOG_error << "Integrity check in Device Model failed: " << e.what();
        throw e;
    }
}

bool DeviceModel::update_monitor_reference(std::int32_t monitor_id, const std::string& reference_value) {
    bool found_monitor = false;
    VariableMonitoringMeta* monitor_meta = nullptr;

    // See if this is a trivial delta monitor and that it exists
    for (auto& [component, variable_map] : this->device_model_map) {
        bool found_monitor_id = false;

        for (auto& [variable, variable_meta_data] : variable_map) {
            auto it = variable_meta_data.monitors.find(monitor_id);
            if (it != std::end(variable_meta_data.monitors)) {
                auto& characteristics = variable_meta_data.characteristics;

                if ((characteristics.dataType == DataEnum::boolean) || (characteristics.dataType == DataEnum::string) ||
                    (characteristics.dataType == DataEnum::dateTime) ||
                    (characteristics.dataType == DataEnum::OptionList) ||
                    (characteristics.dataType == DataEnum::MemberList) ||
                    (characteristics.dataType == DataEnum::SequenceList) &&
                        (it->second.monitor.type == MonitorEnum::Delta)) {
                    monitor_meta = &it->second;
                    found_monitor = true;
                } else {
                    found_monitor = false;
                }

                found_monitor_id = true;
                break; // Break inner loop
            }
        }

        if (found_monitor_id) {
            break; // Break outer loop
        }
    }

    if (found_monitor) {
        try {
            if (this->device_model->update_monitoring_reference(monitor_id, reference_value)) {
                // Update value in-memory too
                monitor_meta->reference_value = reference_value;
                return true;
            }
            EVLOG_warning << "Could not update in DB trivial delta monitor with ID: " << monitor_id
                          << ". Reference value not updated!";

        } catch (const everest::db::Exception& e) {
            EVLOG_error << "Exception while updating trivial delta monitor reference with ID: " << monitor_id;
            throw DeviceModelError(e.what());
        }
    } else {
        EVLOG_warning << "Could not find trivial delta monitor with ID: " << monitor_id
                      << ". Reference value not updated!";
    }

    return false;
}

std::vector<SetMonitoringResult> DeviceModel::set_monitors(const std::vector<SetMonitoringData>& requests,
                                                           const VariableMonitorType type) {
    if (requests.empty()) {
        return {};
    }

    std::vector<SetMonitoringResult> set_monitors_res;

    for (auto& request : requests) {
        SetMonitoringResult result;

        // Set the com/var based on the request since it's required in a response
        // even if it is 'UnknownComoponent/Variable'
        result.component = request.component;
        result.variable = request.variable;
        result.severity = request.severity;
        result.type = request.type;
        result.id = request.id;

        // N04.FR.16 - If we find a monitor with this ID, and there's a comp/var mismatch, send a rejected result
        // N04.FR.13 - If we receive an ID but we can't find a monitor with this ID send a rejected result
        const bool request_has_id = request.id.has_value();
        bool id_found = false;

        if (request_has_id) {
            // Search through all the ID's
            for (const auto& [component, variable_map] : this->device_model_map) {
                for (const auto& [variable, variable_meta] : variable_map) {
                    if (variable_meta.monitors.find(request.id.value()) != std::end(variable_meta.monitors)) {
                        id_found = true;
                        break;
                    }
                }

                if (id_found) {
                    break;
                }
            }

            if (!id_found) {
                result.status = SetMonitoringStatusEnum::Rejected;
                set_monitors_res.push_back(result);
                continue;
            }
        }

        auto component_it = this->device_model_map.find(request.component);

        if (component_it == this->device_model_map.end()) {
            // N04.FR.16
            if (request_has_id && id_found) {
                result.status = SetMonitoringStatusEnum::Rejected;
            } else {
                result.status = SetMonitoringStatusEnum::UnknownComponent;
            }

            set_monitors_res.push_back(result);
            continue;
        }

        auto& variable_map = this->device_model_map[request.component];

        auto variable_it = variable_map.find(request.variable);
        if (variable_it == variable_map.end()) {
            // N04.FR.16
            if (request_has_id && id_found) {
                result.status = SetMonitoringStatusEnum::Rejected;
            } else {
                result.status = SetMonitoringStatusEnum::UnknownVariable;
            }

            set_monitors_res.push_back(result);
            continue;
        }

        // Validate the data we want to set based on the characteristics and
        // see if it is out of range or out of the variable list
        const auto& characteristics = variable_it->second.characteristics;
        bool valid_value = true;

        if (characteristics.supportsMonitoring) {
            EVLOG_debug << "Validating monitor request of type: [" << request << "] and characteristics: ["
                        << characteristics << "]"
                        << " and value: " << request.value;

            // If the monitor is of type 'delta' (or periodic) and it is of a non-numeric type
            // the value is ignored since all values will be reported (excepting negative values)
            if (request.type == MonitorEnum::Delta && std::signbit(request.value)) {
                // N04.FR.14
                valid_value = false;
            } else if (request.type == MonitorEnum::Delta &&
                       (characteristics.dataType != DataEnum::decimal &&
                        characteristics.dataType != DataEnum::integer)) { // NOLINT(bugprone-branch-clone): readability
                valid_value = true;
            } else if (request.type == MonitorEnum::Periodic || request.type == MonitorEnum::PeriodicClockAligned) {
                valid_value = true;
            } else {
                try {
                    valid_value = validate_value(characteristics, std::to_string(request.value),
                                                 allow_zero(request.component, request.variable));
                } catch (const std::exception& e) {
                    EVLOG_warning << "Could not validate monitor value: " << request.value
                                  << " for component: " << request.component << " and variable: " << request.variable;
                    valid_value = false;
                }
            }
        } else {
            valid_value = false;
        }

        if (!valid_value) {
            result.status = SetMonitoringStatusEnum::Rejected;
            set_monitors_res.push_back(result);
            continue;
        }

        // 3.77 Duplicate - A monitor already exists for the given type/severity combination.
        bool duplicate_value = false;

        // Only test for duplicates if we do not receive an explicit monitor ID
        if (!request_has_id) {
            for (const auto& [id, monitor_meta] : variable_it->second.monitors) {
                if (monitor_meta.monitor.type == request.type && monitor_meta.monitor.severity == request.severity) {
                    duplicate_value = true;
                    break;
                }
            }
        }

        if (duplicate_value) {
            result.status = SetMonitoringStatusEnum::Duplicate;
            set_monitors_res.push_back(result);
            continue;
        }

        try {
            auto monitor_meta = this->device_model->set_monitoring_data(request, type);

            if (monitor_meta.has_value()) {
                // N07.FR.11
                // In case of an existing monitor update
                if (request_has_id && monitor_update_listener) {
                    auto attribute = this->device_model->get_variable_attribute(component_it->first, variable_it->first,
                                                                                AttributeEnum::Actual);

                    if (attribute.has_value()) {
                        static const std::string empty_value{};
                        const auto& current_value = attribute.value().value.value_or(empty_value);

                        monitor_update_listener(monitor_meta.value(), component_it->first, variable_it->first,
                                                characteristics, attribute.value(), current_value);
                    } else {
                        EVLOG_warning << "Could not notify monitor update listener, missing variable attribute: "
                                      << variable_it->first;
                    }
                }

                // If we had a successful insert, add/replace it to the variable monitor map
                variable_it->second.monitors[monitor_meta.value().monitor.id] = std::move(monitor_meta.value());

                result.id = monitor_meta.value().monitor.id;
                result.status = SetMonitoringStatusEnum::Accepted;
            } else {
                result.status = SetMonitoringStatusEnum::Rejected;
            }
        } catch (const everest::db::Exception& e) {
            EVLOG_error << "Set monitors failed:" << e.what();
            throw DeviceModelError(e.what());
        }

        set_monitors_res.push_back(result);
    }

    return set_monitors_res;
}

std::vector<VariableMonitoringPeriodic> DeviceModel::get_periodic_monitors() {
    std::vector<VariableMonitoringPeriodic> periodics;

    for (const auto& [component, variable_map] : this->device_model_map) {
        for (const auto& [variable, variable_metadata] : variable_map) {
            std::vector<VariableMonitoringMeta> monitors;

            for (const auto& [id, monitor_meta] : variable_metadata.monitors) {
                if (monitor_meta.monitor.type == MonitorEnum::Periodic ||
                    monitor_meta.monitor.type == MonitorEnum::PeriodicClockAligned) {
                    monitors.push_back(monitor_meta);
                }
            }

            if (!monitors.empty()) {
                periodics.push_back({component, variable, monitors});
            }
        }
    }

    return periodics;
}

std::vector<MonitoringData> DeviceModel::get_monitors(const std::vector<MonitoringCriterionEnum>& criteria,
                                                      const std::vector<ComponentVariable>& component_variables) {
    std::vector<MonitoringData> get_monitors_res{};

    if (!component_variables.empty()) {
        for (auto& component_variable : component_variables) {
            // Case not handled by spec, skipping
            if (this->device_model_map.find(component_variable.component) == this->device_model_map.end()) {
                continue;
            }

            auto& variable_map = this->device_model_map[component_variable.component];

            // N02.FR.16 - if variable is missing, report all existing variables inside that component
            if (component_variable.variable.has_value() == false) {
                for (const auto& [variable, variable_meta] : variable_map) {
                    MonitoringData monitor_data;

                    monitor_data.component = component_variable.component;
                    monitor_data.variable = variable;

                    for (const auto& [id, monitor_meta] : variable_meta.monitors) {
                        if (ocpp::v2::utils::filter_criteria_monitor(criteria, monitor_meta)) {
                            monitor_data.variableMonitoring.push_back(monitor_meta.monitor);
                        }
                    }

                    if (!monitor_data.variableMonitoring.empty()) {
                        get_monitors_res.push_back(std::move(monitor_data));
                    }
                }
            } else {
                auto variable_it = variable_map.find(component_variable.variable.value());

                // Case not handled by spec, skipping
                if (variable_it == variable_map.end()) {
                    continue;
                }

                MonitoringData monitor_data;

                monitor_data.component = component_variable.component;
                monitor_data.variable = variable_it->first;

                auto& variable_meta = variable_it->second;

                for (const auto& [id, monitor_meta] : variable_meta.monitors) {
                    if (ocpp::v2::utils::filter_criteria_monitor(criteria, monitor_meta)) {
                        monitor_data.variableMonitoring.push_back(monitor_meta.monitor);
                    }
                }

                if (!monitor_data.variableMonitoring.empty()) {
                    get_monitors_res.push_back(std::move(monitor_data));
                }
            }
        }
    } else {
        // N02.FR.11 - if criteria and component_variables are empty, return all existing monitors
        for (const auto& [component, variable_map] : this->device_model_map) {
            for (const auto& [variable, variable_metadata] : variable_map) {
                std::vector<VariableMonitoring> monitors;

                for (const auto& [id, monitor_meta] : variable_metadata.monitors) {
                    // Also handles the case when the criteria is empty,
                    // since in that case N02.FR.11 applies (all monitors pass)
                    if (ocpp::v2::utils::filter_criteria_monitor(criteria, monitor_meta)) {
                        monitors.push_back(monitor_meta.monitor);
                    }
                }

                if (!monitors.empty()) {
                    get_monitors_res.push_back({component, variable, monitors, std::nullopt});
                }
            }
        }
    }

    return get_monitors_res;
}
std::vector<ClearMonitoringResult> DeviceModel::clear_monitors(const std::vector<int>& request_ids,
                                                               bool allow_protected) {
    if (request_ids.empty()) {
        return {};
    }

    std::vector<ClearMonitoringResult> clear_monitors_vec;

    for (auto& id : request_ids) {
        ClearMonitoringResult clear_monitor_res;
        clear_monitor_res.id = id;

        try {
            auto clear_result = this->device_model->clear_variable_monitor(id, allow_protected);
            if (clear_result == ClearMonitoringStatusEnum::Accepted) {
                // Clear from memory too
                for (auto& [component, variable_map] : this->device_model_map) {
                    for (auto& [variable, variable_metadata] : variable_map) {
                        variable_metadata.monitors.erase(static_cast<std::int64_t>(id));
                    }
                }
            }

            clear_monitor_res.status = clear_result;
        } catch (const everest::db::Exception& e) {
            EVLOG_error << "Clear monitors failed:" << e.what();
            throw DeviceModelError(e.what());
        }

        clear_monitors_vec.push_back(clear_monitor_res);
    }

    return clear_monitors_vec;
}

std::int32_t DeviceModel::clear_custom_monitors() {
    try {
        const std::int32_t deleted = this->device_model->clear_custom_variable_monitors();

        // Clear from memory too
        for (auto& [component, variable_map] : this->device_model_map) {
            for (auto& [variable, variable_metadata] : variable_map) {
                // Delete while iterating all custom monitors
                for (auto it = variable_metadata.monitors.begin(); it != variable_metadata.monitors.end();) {
                    if (it->second.type == VariableMonitorType::CustomMonitor) {
                        it = variable_metadata.monitors.erase(it);
                    } else {
                        ++it;
                    }
                }
            }
        }

        return deleted;
    } catch (const everest::db::Exception& e) {
        EVLOG_error << "Clear custom monitors failed:" << e.what();
        throw DeviceModelError(e.what());
    }

    return 0;
}

} // namespace v2
} // namespace ocpp
