#include "esp_log.h"

#include "ocpp.h"
#include "evse_api.h"
#include "evse_error.h"
#include "evse_state.h"

#include "esp_wifi.h"
#include "orno_modbus.h"
#include "nvs.h"

/* MicroOcpp includes */
#include <mongoose.h>
#include <MicroOcpp_c.h>               //C-facade of MicroOcpp
#include <MicroOcppMongooseClient_c.h> //WebSocket integration for ESP-IDF

#define NVS_NAMESPACE "ocpp"

#define NVS_OCPP_AUTHORIZATION "authorization"
#define NVS_OCPP_ENABLED "enabled"
#define NVS_OCPP_SERVER "ocpp_server"
#define NVS_OCPP_RFID "ocpp_rfid"

#define AP_SSID "PLX-%02x%02x%02x"

static nvs_handle nvs;

static const char *TAG = "ocpp";

static bool enabled = true;

static bool authorization = false;

// static bool connector_plugged = false;

// static bool is_charging = false;

static TaskHandle_t ocpp_task = NULL;

static struct mg_mgr mgr; // Event manager

static void ocpp_task_func(void *param)
{

    while (true)
    {
        // if (enabled)
        {
            mg_mgr_poll(&mgr, 10);
            ocpp_loop();

            if (evse_is_enabled() != ocpp_isOperative())
            {
                printf("ocpp_isOperative()");
                evse_set_enabled(ocpp_isOperative());
            }
        }
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

bool ocpp_get_enabled(void)
{
    uint8_t value = false;
    nvs_get_u8(nvs, NVS_OCPP_ENABLED, &value);
    ESP_LOGI(TAG, "Ocpp get enabled %d", value);
    return value;
}

bool ocpp_get_authorization(void)
{
    uint8_t value = false;
    nvs_get_u8(nvs, NVS_OCPP_AUTHORIZATION, &value);
    ESP_LOGI(TAG, "Ocpp get authorization %d", value);
    return value;
}

void ocpp_get_server(char *value)
{
    size_t len = 64;
    value[0] = '\0';
    nvs_get_str(nvs, NVS_OCPP_SERVER, value, &len);

    ESP_LOGI(TAG, "Ocpp get server %s", value);
}

void ocpp_get_rfid(char *value)
{
    size_t len = 64;
    value[0] = '\0';
    nvs_get_str(nvs, NVS_OCPP_RFID, value, &len);

    ESP_LOGI(TAG, "Ocpp get rfid %s", value);
}

void ocpp_set_authorization(bool value)
{
    ESP_LOGI(TAG, "Ocpp set authorization %d", value);
    nvs_set_u8(nvs, NVS_OCPP_ENABLED, value);
    nvs_commit(nvs);
    authorization = value;
}

void ocpp_set_enabled(bool value)
{
    ESP_LOGI(TAG, "Ocpp set enabled %d", value);
    nvs_set_u8(nvs, NVS_OCPP_ENABLED, value);
    nvs_commit(nvs);
    enabled = value;
}

void ocpp_set_server(char *value)
{
    ESP_LOGI(TAG, "Ocpp set server %s", value);

    nvs_set_str(nvs, NVS_OCPP_SERVER, value);

    nvs_commit(nvs);
}

void ocpp_set_rfid(char *value)
{
    ESP_LOGI(TAG, "Ocpp set rfid %s", value);

    nvs_set_str(nvs, NVS_OCPP_RFID, value);

    nvs_commit(nvs);
}

bool setConnectorPluggedInput()
{
    // ESP_LOGI(TAG, "setConnectorPluggedInput");
    return evse_is_connector_plugged(evse_get_state());
    // return true;
}

bool setEvReadyInput()
{
    // ESP_LOGI(TAG, "EvReadyInput");
    return evse_state_is_charging(evse_get_state());
    // return is_charging;
}

bool setEvseReadyInput()
{
    // ESP_LOGI(TAG, "EvseReadyInput");
    return evse_is_enabled();
    // return false;
}

float setPowerMeterInput()
{
    ESP_LOGI(TAG, "PowerMeterInput");
    //MeterData data = meter_getData();
    //return data.wattA + data.wattB + data.wattC;
    return 0.0f;
}

float setEnergyMeterInput()
{
    ESP_LOGI(TAG, "EnergyMeterInput");

    // Exemplo com valor fixo (pode ser substituído depois)
    return 3600.0f;
}

int setEnergyInput()
{
    ESP_LOGI(TAG, "EnergyInput");

    // Exemplo com valor fixo
    return 3600;
}

float setCurrentInput()
{
    ESP_LOGI(TAG, "CurrentInput");

    /*
    if (!meter_is_running()) {
        ESP_LOGW(TAG, "Meter not running, returning fallback.");
        return 0.0f;
    }

    MeterData data = meter_getData();
    return data.irmsA;
    */
    return 0;
}

float setVoltageInput()
{
    ESP_LOGI(TAG, "VoltageInput");

    /*
    if (!meter_is_running()) {
        ESP_LOGW(TAG, "Meter not running, returning fallback.");
        return 0.0f;
    }

    MeterData data = meter_getData();
    return data.vrmsA;
    */
    return 0.0f;
}

float setTemperatureInput()
{
    ESP_LOGI(TAG, "TemperatureInput");
    return 16.5f;
}

float setPowerInput()
{
    ESP_LOGI(TAG, "PowerInput");
    // return (float)orno_modbus_get_meter_state().activepower;
    //return meter_getData().wattA + meter_getData().wattB + meter_getData().wattC;
    return 0;
}

void setSmartChargingCurrentOutput(float limit)
{
    ESP_LOGI(TAG, "SmartChargingCurrentOutput: %.0f\n", limit);
};

void setSmartChargingPowerOutput(float limit)
{
    ESP_LOGI(TAG, "SmartChargingPowerOutput: %.0f\n", limit);
};

void setSmartChargingOutput(float power, float current, int nphases)
{
    ESP_LOGI(TAG, " SmartChargingOutput: %.0f %.0f \n", power, current);
};

void setGetConfiguration(const char *payload, size_t len)
{
    ESP_LOGI(TAG, " setGetConfiguration: %s %d \n", payload, len);
}

void setStartTransaction(const char *payload, size_t len)
{
    ESP_LOGI(TAG, " setStartTransaction: %s %d \n", payload, len);
}

void setChangeConfiguration(const char *payload, size_t len)
{
    ESP_LOGI(TAG, " setChangeConfiguration: %s %d \n", payload, len);
}

void OnResetExecute(bool state)
{
    ESP_LOGI(TAG, "#### OnResetExecute");
    esp_restart();
}

bool setOccupiedInput()
{
    ESP_LOGI(TAG, "setOccupiedInput");
    return false;
}

bool setStartTxReadyInput()
{
    ESP_LOGI(TAG, "!!!!!! StartTxReadyInput");
    return false;
}

bool setStopTxReadyInput()
{
    ESP_LOGI(TAG, "===== StopTxReadyInput");
    return true;
}

/*
enum OptionalBool setOnUnlockConnectorInOut()
{
    ESP_LOGI(TAG, "***** OnUnlockConnectorInOut");
    return OptionalTrue;
}*/

bool setOnResetNotify(bool value)
{
    ESP_LOGI(TAG, "!!!!!! setOnResetNotify %d", value);
    return true;
}

void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification txNotification)
{
    ESP_LOGI(TAG, "Set notificationOutput ----> %d", txNotification);

    switch (txNotification)
    {
    // Authorization events
    case Authorized:
        ESP_LOGI(TAG, "<-----------  Authorized ---------->");
        evse_authorize();
        // is_charging = true;
        break; // success
    case AuthorizationRejected:
        ESP_LOGI(TAG, "AuthorizationRejected ---->");
        break; // IdTag not authorized
    case AuthorizationTimeout:
        ESP_LOGI(TAG, "AuthorizationTimeout ---->");
        break; // authorization failed - offline
    case ReservationConflict:
        ESP_LOGI(TAG, "ReservationConflict ---->");
        break; // connector reserved for other IdTag
    case ConnectionTimeout:
        ESP_LOGI(TAG, "ConnectionTimeout ---->");
        break; // user took to long to plug vehicle after the authorization
    case DeAuthorized:
        ESP_LOGI(TAG, "DeAuthorized ---->");
        evse_set_authorized(false);
        evse_set_limit_reached(2);
        // ocpp_set_charging(false);
        break; // server rejected StartTx
    case RemoteStart:
        ESP_LOGI(TAG, "RemoteStart ---->");
        break; // authorized via RemoteStartTransaction
    case RemoteStop:
        ESP_LOGI(TAG, "RemoteStop ---->");
        break; // stopped via RemoteStopTransaction

    // Tx lifecycle events
    case StartTx:
        ESP_LOGI(TAG, "StartTx ---->");
        break;
    case StopTx:
        // is_charging = false;
        ESP_LOGI(TAG, "StopTx ---->");
        evse_set_authorized(false);
        evse_set_limit_reached(2);
        break;
    };
}

const char *addErrorCodeInput()
{
    // ESP_LOGI(TAG, "AddErrorCodeInput");

    char *ptr = NULL;

    uint32_t error = 0; // evse_get_error();

    // ESP_LOGI(TAG, "AddErrorCodeInput %" PRIu32 "", error);

    if (error & EVSE_ERR_PILOT_FAULT_BIT)
    {
        ptr = "InternalError";
    }
    else if (error & EVSE_ERR_DIODE_SHORT_BIT)
    {
        ptr = "InternalError";
    }
    else if (error & EVSE_ERR_LOCK_FAULT_BIT)
    {
        ptr = "ConnectorLockFailure";
    }
    else if (error & EVSE_ERR_UNLOCK_FAULT_BIT)
    {
        ptr = "ConnectorLockFailure";
    }
    else if (error & EVSE_ERR_RCM_TRIGGERED_BIT)
    {
        ptr = "OtherError";
    }
    else if (error & EVSE_ERR_RCM_SELFTEST_FAULT_BIT)
    {
        ptr = "OtherError";
    }
    else if (error & EVSE_ERR_TEMPERATURE_HIGH_BIT)
    {
        ptr = "HighTemperature";
    }
    else if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
    {
        ptr = "OtherError";
    }

    return ptr;
}

void ocpp_begin_transaction(char *value)
{
    ocpp_beginTransaction(value);
}

void ocpp_end_transaction(char *value)
{
    ocpp_endTransaction(value, "Local");
}

void ocpp_begin_transaction_authorized(char *value)
{
    ocpp_beginTransaction_authorized(value, NULL);
}

void ocpp_end_transaction_authorized(char *value)
{
    ocpp_endTransaction_authorized(value, "Local");
}

bool ocpp_is_TransactionActive()
{
    return ocpp_isTransactionActive();
}

void ocpp_start()
{
    ESP_LOGI(TAG, "Starting ocpp");

    ESP_ERROR_CHECK(nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs));

    // enabled = ocpp_get_enabled();
    authorization = ocpp_get_authorization();

    char serverstr[64];
    ocpp_get_server(serverstr);
    char rfidstr[64];
    ocpp_get_rfid(rfidstr);

    /* Initialize Mongoose (necessary for MicroOcpp)*/
    // struct mg_mgr mgr;       // Event manager
    mg_mgr_init(&mgr);       // Initialise event manager
    mg_log_set(MG_LL_ERROR); // Set log level

    /* Initialize MicroOcpp */
    struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true};

    char chargeid[12];
    uint8_t mac[6];
    esp_wifi_get_mac(ESP_IF_WIFI_AP, mac);
    sprintf((char *)chargeid, AP_SSID, mac[3], mac[4], mac[5]);

    char *urlid = (char *)malloc(62 * sizeof(char));
    sprintf(urlid, "%s", strupr(chargeid));

    //"ws://192.168.1.164:3000",

    // ws://192.168.1.115:8010/OCPP16/5c866e81a2d9593de43efdb4/6750f0235b1b277aa16caf19

    OCPP_Connection *osock = ocpp_makeConnection(&mgr,
                                                 "ws://192.168.2.217:8010/OCPP16/5c866e81a2d9593de43efdb4/6750f0235b1b277aa16caf19/",
                                                 "Plx_00AA1",
                                                 "",
                                                 "", fsopt);

    ocpp_initialize(osock, "EPower M1", "Plixin", fsopt, false);

    // ocpp_authorize()

    // ocpp_authorize(rfidstr,NULL,NULL,NULL,NULL,NULL);

    // ocpp_authorize("2D3CF08E", NULL, NULL, NULL, NULL, NULL);

    // 2D3CF08E

    /*
  * Load OCPP configs. Default values will be overwritten by OpenEVSE configs. Mark configs
  * to require reboot if changed via OCPP server

 freevendActive = ArduinoOcpp::declareConfiguration<bool>("AO_FreeVendActive", true, CONFIGURATION_FN, true, true, true, true);
 freevendIdTag = ArduinoOcpp::declareConfiguration<const char*>("AO_FreeVendIdTag", "DefaultIdTag", CONFIGURATION_FN, true, true, true, true);
      */
    // allowOfflineTxForUnknownId = ArduinoOcpp::declareConfiguration<bool>("AllowOfflineTxForUnknownId", true, CONFIGURATION_FN, true, true, true, true);
    // silentOfflineTx = ArduinoOcpp::declareConfiguration<bool>("AO_SilentOfflineTransactions", true, CONFIGURATION_FN, true, true, true, true);

    /*
        //when the OCPP server updates the configs, the following callback will apply them to the OpenEVSE configs
        setOnReceiveRequest("ChangeConfiguration", [this] (JsonObject) {

            config_set("ocpp_auth_auto", (uint32_t) (*freevendActive ? 1 : 0));
            config_set("ocpp_idtag", String((const char*) *freevendIdTag));
            config_set("ocpp_auth_offline", (uint32_t) (*allowOfflineTxForUnknownId ? 1 : 0));
            config_commit();
        });
    */

    // ocpp_set_authorization

    // ocpp_setEnergyMeterInput(&setEnergyInput);

    // ocpp_setPowerMeterInput(&setPowerMeterInput);

    ocpp_setEvReadyInput(&setEvReadyInput);

    ocpp_setEvseReadyInput(&setEvseReadyInput);

    ocpp_setSmartChargingCurrentOutput(&setSmartChargingCurrentOutput);

    ocpp_setSmartChargingPowerOutput(&setSmartChargingPowerOutput);

    ocpp_setSmartChargingOutput(&setSmartChargingOutput);

    ocpp_setConnectorPluggedInput(&setConnectorPluggedInput);

    ocpp_setOnResetExecute(&OnResetExecute);

    ocpp_setTxNotificationOutput(&notificationOutput);

    // ocpp_setStartTxReadyInput(&setStartTxReadyInput);

    ocpp_setStopTxReadyInput(&setStopTxReadyInput);

    // ocpp_setOnUnlockConnectorInOut(&setOnUnlockConnectorInOut);

    // ocpp_setOccupiedInput(&setOccupiedInput);

    ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL);
    ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Offered", "A", NULL, NULL);
    ocpp_addMeterValueInputFloat(&setVoltageInput, "Voltage", "V", NULL, NULL);
    ocpp_addMeterValueInputFloat(&setTemperatureInput, "Temperature", "Celsius", NULL, NULL);

    ocpp_addMeterValueInputFloat(&setPowerMeterInput, "Power.Active.Import", "W", NULL, NULL);
    ocpp_addMeterValueInputFloat(&setEnergyMeterInput, "Energy.Active.Import.Register", "W", NULL, NULL);

    ocpp_addErrorCodeInput(&addErrorCodeInput);

    ocpp_setOnResetNotify(&setOnResetNotify);

    // ocpp_setOnResetExecute(void (*onResetExecute)(bool));

    // ocpp_setOnReceiveRequest(const char *operationType, OnMessage onRequest);

    // ocpp_setOnSendConf(const char *operationType, OnMessage onConfirmation);

    // when the OCPP server updates the configs, the following callback will apply them to the OpenEVSE configs
    // ocpp_setOnReceiveRequest("ChangeConfiguration", &setChangeConfiguration);

    // ocpp_setOnReceiveRequest("GetConfiguration", &setGetConfiguration);

    // ocpp_setOnReceiveRequest("StartTransaction", &setStartTransaction);

    xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 5, &ocpp_task);
}

void ocpp_stop(void)
{
    ESP_LOGI(TAG, "Stopping");

    if (ocpp_task)
    {
        vTaskDelete(ocpp_task);
        ocpp_task = NULL;
    }

    /* Deallocate ressources */
    ocpp_deinitialize();
    // ocpp_deinitConnection(osock);
    mg_mgr_free(&mgr);
}