// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef OCPP201_ERROR_HANDLING_HPP
#define OCPP201_ERROR_HANDLING_HPP

#include <everest/conversions/ocpp/ocpp_conversions.hpp>
#include <ocpp/v2/ocpp_types.hpp>
#include <utils/error.hpp>

#include <unordered_map>

namespace module {
const std::unordered_map<std::string, std::string> MREC_ERROR_MAP = {
    {"connector_lock/MREC1ConnectorLockFailure", "CX001"},
    {"evse_board_support/MREC2GroundFailure", "CX002"},
    {"evse_board_support/MREC3HighTemperature", "CX003"},
    {"evse_board_support/MREC4OverCurrentFailure", "CX004"},
    {"evse_board_support/MREC5OverVoltage", "CX005"},
    {"evse_board_support/MREC6UnderVoltage", "CX006"},
    {"evse_board_support/MREC8EmergencyStop", "CX008"},
    {"evse_board_support/MREC10InvalidVehicleMode", "CX010"},
    {"evse_board_support/MREC14PilotFault", "CX014"},
    {"evse_board_support/MREC15PowerLoss", "CX015"},
    {"evse_board_support/MREC17EVSEContactorFault", "CX017"},
    {"evse_board_support/MREC18CableOverTempDerate", "CX018"},
    {"evse_board_support/MREC19CableOverTempStop", "CX019"},
    {"evse_board_support/MREC20PartialInsertion", "CX020"},
    {"evse_board_support/MREC23ProximityFault", "CX023"},
    {"evse_board_support/MREC24ConnectorVoltageHigh", "CX024"},
    {"evse_board_support/MREC25BrokenLatch", "CX025"},
    {"evse_board_support/MREC26CutCable", "CX026"},
    {"evse_manager/MREC4OverCurrentFailure", "CX004"},
    {"ac_rcd/MREC2GroundFailure", "CX002"},
    {"evse_manager/MREC22ResistanceFault", "CX022"},
    {"evse_manager/MREC11CableCheckFault", "CX011"},
    {"evse_manager/MREC5OverVoltage", "CX005"},
};

const auto EVSE_MANAGER_INOPERATIVE_ERROR = "evse_manager/Inoperative";
const auto CHARGING_STATION_COMPONENT_NAME = "ChargingStation";
const auto EVSE_COMPONENT_NAME = "EVSE";
const auto CONNECTOR_COMPONENT_NAME = "Connector";
const auto PROBLEM_VARIABLE_NAME = "Problem";

/// \brief Returns simplified mapping from error origin to OCPP component based on evse and connector ids
ocpp::v2::Component get_component_from_error(const Everest::error::Error& error) {
    ocpp::v2::Component component;

    if (!error.origin.mapping.has_value()) {
        // component is ChargingStation
        component.name = CHARGING_STATION_COMPONENT_NAME;
        return component;
    }

    const auto& mapping = error.origin.mapping.value();
    const auto evse_id = mapping.evse;

    if (!mapping.connector.has_value()) {
        // component is EVSE
        ocpp::v2::EVSE evse;
        evse.id = evse_id;
        component.name = EVSE_COMPONENT_NAME;
        component.evse = evse;
        return component;
    }

    // component is Connector
    ocpp::v2::EVSE evse;
    evse.id = evse_id;
    evse.connectorId = mapping.connector.value();
    component.name = EVSE_COMPONENT_NAME;
    component.evse = evse;
    return component;
}

/// \brief Derives the EventData from the given \p error, \p cleared and \p event_id parameters
ocpp::v2::EventData get_event_data(const Everest::error::Error& error, const bool cleared, const int32_t event_id) {
    ocpp::v2::EventData event_data;
    event_data.eventId = event_id; // This can theoretically conflict with eventIds generated in libocpp (e.g.
                                   // for monitoring events), but the spec does not strictly forbid that
    event_data.timestamp = ocpp::DateTime(error.timestamp);
    event_data.trigger = ocpp::v2::EventTriggerEnum::Alerting;
    event_data.cause = std::nullopt; // TODO: use caused_by when available within error object
    event_data.actualValue = cleared ? "false" : "true";

    if (MREC_ERROR_MAP.count(error.type)) {
        event_data.techCode = MREC_ERROR_MAP.at(error.type);
    } else {
        event_data.techCode = error.type;
    }

    event_data.techInfo = error.description;
    event_data.cleared = cleared;
    event_data.transactionId = std::nullopt;        // TODO: Do we need to set this here?
    event_data.variableMonitoringId = std::nullopt; // We dont need to set this for HardwiredNotification
    event_data.eventNotificationType = ocpp::v2::EventNotificationEnum::HardWiredNotification;

    event_data.component = get_component_from_error(error);
    event_data.variable = {PROBLEM_VARIABLE_NAME}; // TODO: use type of error for mapping to variable?
    return event_data;
}
}; // namespace module

#endif // OCPP201_ERROR_HANDLING_HPP
