#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "esp_wifi.h"
#include "esp_ota_ops.h"
#include "esp_timer.h"
#include "esp_chip_info.h"
#include "esp_mac.h"

#include "json.h"
#include "mqtt.h"
#include "wifi.h"
#include "timeout_utils.h"
#include "evse_error.h"
#include "evse_api.h"
#include "ocpp.h"
#include "board_config.h"
#include "meter.h"
#include "socket_lock.h"
#include "proximity.h"
//#include "modbus.h"
//#include "modbus_tcp.h"
#include "rest.h"
#include "temp_sensor.h"
// #include "script.h"
#include "date_time.h"

#define RETURN_ON_ERROR(x)               \
    do                                   \
    {                                    \
        esp_err_t err_rc_ = (x);         \
        if (unlikely(err_rc_ != ESP_OK)) \
        {                                \
            return err_rc_;              \
        }                                \
    } while (0)

cJSON *json_get_evse_config(void)
{
    cJSON *root = cJSON_CreateObject();

    cJSON_AddNumberToObject(root, "maxChargingCurrent", evse_get_max_charging_current());
    cJSON_AddNumberToObject(root, "chargingCurrent", evse_get_charging_current() / 10.0);
    cJSON_AddNumberToObject(root, "defaultChargingCurrent", evse_get_default_charging_current() / 10.0);
    cJSON_AddBoolToObject(root, "requireAuth", evse_is_require_auth());
    cJSON_AddBoolToObject(root, "socketOutlet", evse_get_socket_outlet());
    cJSON_AddBoolToObject(root, "rcm", evse_is_rcm());
    cJSON_AddNumberToObject(root, "temperatureThreshold", evse_get_temp_threshold());
    cJSON_AddNumberToObject(root, "consumptionLimit", evse_get_consumption_limit());
    cJSON_AddNumberToObject(root, "defaultConsumptionLimit", evse_get_default_consumption_limit());
    cJSON_AddNumberToObject(root, "chargingTimeLimit", evse_get_charging_time_limit());
    cJSON_AddNumberToObject(root, "defaultChargingTimeLimit", evse_get_default_charging_time_limit());
    cJSON_AddNumberToObject(root, "underPowerLimit", evse_get_under_power_limit());
    cJSON_AddNumberToObject(root, "defaultUnderPowerLimit", evse_get_default_under_power_limit());

    cJSON_AddNumberToObject(root, "socketLockOperatingTime", socket_lock_get_operating_time());
    cJSON_AddNumberToObject(root, "socketLockBreakTime", socket_lock_get_break_time());
    cJSON_AddBoolToObject(root, "socketLockDetectionHigh", socket_lock_is_detection_high());
    cJSON_AddNumberToObject(root, "socketLockRetryCount", socket_lock_get_retry_count());

    cJSON_AddStringToObject(root, "modelMeter", meter_model_to_str(meter_get_model()));
    cJSON_AddNumberToObject(root, "maxGridCurrent", grid_get_max_current());
    cJSON_AddStringToObject(root, "stateMeter", meter_state_to_str(meter_get_state()));

    char str[64];
    cJSON_AddBoolToObject(root, "enabledocpp", ocpp_get_enabled());
    ocpp_get_server(str);
    cJSON_AddStringToObject(root, "serverocpp", str);
    ocpp_get_rfid(str);
    cJSON_AddStringToObject(root, "rfid", str);

    return root;
}

esp_err_t json_set_evse_config(cJSON *root)
{
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "maxChargingCurrent")))
    {
        RETURN_ON_ERROR(evse_set_max_charging_current(cJSON_GetObjectItem(root, "maxChargingCurrent")->valuedouble));
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "chargingCurrent")))
    {
        RETURN_ON_ERROR(evse_set_charging_current(cJSON_GetObjectItem(root, "chargingCurrent")->valuedouble * 10));
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "defaultChargingCurrent")))
    {
        RETURN_ON_ERROR(evse_set_default_charging_current(cJSON_GetObjectItem(root, "defaultChargingCurrent")->valuedouble * 10));
    }
    if (cJSON_IsBool(cJSON_GetObjectItem(root, "requireAuth")))
    {
        evse_set_require_auth(cJSON_IsTrue(cJSON_GetObjectItem(root, "requireAuth")));
    }
    if (cJSON_IsBool(cJSON_GetObjectItem(root, "socketOutlet")))
    {
        RETURN_ON_ERROR(evse_set_socket_outlet(cJSON_IsTrue(cJSON_GetObjectItem(root, "socketOutlet"))));
    }
    if (cJSON_IsBool(cJSON_GetObjectItem(root, "rcm")))
    {
        RETURN_ON_ERROR(evse_set_rcm(cJSON_IsTrue(cJSON_GetObjectItem(root, "rcm"))));
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "temperatureThreshold")))
    {
        RETURN_ON_ERROR(evse_set_temp_threshold(cJSON_GetObjectItem(root, "temperatureThreshold")->valuedouble));
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "consumptionLimit")))
    {
        evse_set_consumption_limit(cJSON_GetObjectItem(root, "consumptionLimit")->valuedouble);
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "defaultConsumptionLimit")))
    {
        evse_set_default_consumption_limit(cJSON_GetObjectItem(root, "defaultConsumptionLimit")->valuedouble);
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "chargingTimeLimit")))
    {
        evse_set_charging_time_limit(cJSON_GetObjectItem(root, "chargingTimeLimit")->valuedouble);
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "defaultChargingTimeLimit")))
    {
        evse_set_default_charging_time_limit(cJSON_GetObjectItem(root, "defaultChargingTimeLimit")->valuedouble);
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "underPowerLimit")))
    {
        evse_set_under_power_limit(cJSON_GetObjectItem(root, "underPowerLimit")->valuedouble);
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "defaultUnderPowerLimit")))
    {
        evse_set_default_under_power_limit(cJSON_GetObjectItem(root, "defaultUnderPowerLimit")->valuedouble);
    }

    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "socketLockOperatingTime")))
    {
        RETURN_ON_ERROR(socket_lock_set_operating_time(cJSON_GetObjectItem(root, "socketLockOperatingTime")->valuedouble));
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "socketLockBreakTime")))
    {
        RETURN_ON_ERROR(socket_lock_set_break_time(cJSON_GetObjectItem(root, "socketLockBreakTime")->valuedouble));
    }
    if (cJSON_IsBool(cJSON_GetObjectItem(root, "socketLockDetectionHigh")))
    {
        socket_lock_set_detection_high(cJSON_IsTrue(cJSON_GetObjectItem(root, "socketLockDetectionHigh")));
    }
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "socketLockRetryCount")))
    {
        socket_lock_set_retry_count(cJSON_GetObjectItem(root, "socketLockRetryCount")->valuedouble);
    }

    if (cJSON_IsString(cJSON_GetObjectItem(root, "modelMeter")))
    {
        RETURN_ON_ERROR(meter_set_model(meter_str_to_model(cJSON_GetObjectItem(root, "modelMeter")->valuestring)));
    }

    /*
        if (cJSON_IsString(cJSON_GetObjectItem(root, "stateMeter")))
        {
            RETURN_ON_ERROR(meter_set_state(meter_str_to_state(cJSON_GetObjectItem(root, "stateMeter")->valuestring)));
        }
    */
    if (cJSON_IsNumber(cJSON_GetObjectItem(root, "maxGridCurrent")))
    {
        RETURN_ON_ERROR(grid_set_max_current(cJSON_GetObjectItem(root, "maxGridCurrent")->valuedouble));
    }

    if (cJSON_IsBool(cJSON_GetObjectItem(root, "enabledocpp")))
    {
        ocpp_set_enabled(cJSON_IsTrue(cJSON_GetObjectItem(root, "enabledocpp")));
    }

    if (cJSON_IsString(cJSON_GetObjectItem(root, "serverocpp")))
    {
        ocpp_set_server(cJSON_GetStringValue(cJSON_GetObjectItem(root, "serverocpp")));
    }

    if (cJSON_IsString(cJSON_GetObjectItem(root, "rfid")))
    {
        ocpp_set_rfid(cJSON_GetStringValue(cJSON_GetObjectItem(root, "rfid")));
    }

    return ESP_OK;
}

cJSON *json_get_wifi_config(void)
{
    cJSON *root = cJSON_CreateObject();

    char str[32];

    cJSON_AddBoolToObject(root, "enabled", wifi_get_enabled());
    wifi_get_ssid(str);
    cJSON_AddStringToObject(root, "ssid", str);

    return root;
}

esp_err_t json_set_wifi_config(cJSON *root, bool timeout)
{
    bool enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "enabled"));
    char *ssid = cJSON_GetStringValue(cJSON_GetObjectItem(root, "ssid"));
    char *password = cJSON_GetStringValue(cJSON_GetObjectItem(root, "password"));

    if (timeout)
    {
        return timeout_wifi_set_config(enabled, ssid, password);
    }
    else
    {
        return wifi_set_config(enabled, ssid, password);
    }
}

cJSON *json_get_wifi_scan(void)
{
    cJSON *root = cJSON_CreateArray();

    wifi_scan_ap_t scan_aps[WIFI_SCAN_SCAN_LIST_SIZE];
    uint16_t number = wifi_scan(scan_aps);
    for (int i = 0; i < number; i++)
    {
        cJSON *item = cJSON_CreateObject();
        cJSON_AddStringToObject(item, "ssid", scan_aps[i].ssid);
        cJSON_AddNumberToObject(item, "rssi", scan_aps[i].rssi);
        cJSON_AddBoolToObject(item, "auth", scan_aps[i].auth);
        cJSON_AddItemToArray(root, item);
    }

    return root;
}

cJSON *json_get_mqtt_config(void)
{
    cJSON *root = cJSON_CreateObject();

    char str[64];

    cJSON_AddBoolToObject(root, "enabled", mqtt_get_enabled());
    mqtt_get_server(str);
    cJSON_AddStringToObject(root, "server", str);
    mqtt_get_base_topic(str);
    cJSON_AddStringToObject(root, "baseTopic", str);
    mqtt_get_user(str);
    cJSON_AddStringToObject(root, "user", str);
    mqtt_get_password(str);
    cJSON_AddStringToObject(root, "password", str);
    cJSON_AddNumberToObject(root, "periodicity", mqtt_get_periodicity());

    return root;
}

esp_err_t json_set_mqtt_config(cJSON *root)
{
    bool enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "enabled"));
    char *server = cJSON_GetStringValue(cJSON_GetObjectItem(root, "server"));
    char *base_topic = cJSON_GetStringValue(cJSON_GetObjectItem(root, "baseTopic"));
    char *user = cJSON_GetStringValue(cJSON_GetObjectItem(root, "user"));
    char *password = cJSON_GetStringValue(cJSON_GetObjectItem(root, "password"));
    uint16_t periodicity = cJSON_GetNumberValue(cJSON_GetObjectItem(root, "periodicity"));

    return mqtt_set_config(enabled, server, base_topic, user, password, periodicity);
}



cJSON *json_get_modbus_config(void)
{
    cJSON *root = cJSON_CreateObject();

    //cJSON_AddBoolToObject(root, "tcpEnabled", modbus_tcp_is_enabled());
    //cJSON_AddNumberToObject(root, "unitId", modbus_get_unit_id());

    cJSON_AddBoolToObject(root, "tcpEnabled", false);
    cJSON_AddNumberToObject(root, "unitId", 1);

    return root;
}

esp_err_t json_set_modbus_config(cJSON *root)
{
    //bool tcp_enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "tcpEnabled"));
    //uint8_t unit_id = cJSON_GetObjectItem(root, "unitId")->valuedouble;

    //modbus_tcp_set_enabled(tcp_enabled);
    return 0; //modbus_set_unit_id(unit_id);
}
/*

cJSON* json_get_script_config(void)
{
    cJSON* root = cJSON_CreateObject();

    cJSON_AddBoolToObject(root, "enabled", script_is_enabled());

    return root;
}

esp_err_t json_set_script_config(cJSON* root)
{
    bool enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "enabled"));

    script_set_enabled(enabled);

    return ESP_OK;
}*/

cJSON *json_get_time_config(void)
{
    cJSON *root = cJSON_CreateObject();
    char str[64];

    cJSON_AddBoolToObject(root, "ntpEnabled", date_time_is_ntp_enabled());
    date_time_get_ntp_server(str);
    cJSON_AddStringToObject(root, "ntpServer", str);
    cJSON_AddBoolToObject(root, "ntpFromDhcp", date_time_is_ntp_from_dhcp());
    date_time_get_timezone(str);
    cJSON_AddStringToObject(root, "timezone", str);

    return root;
}

esp_err_t json_set_time_config(cJSON *root)
{
    char *ntp_server = cJSON_GetStringValue(cJSON_GetObjectItem(root, "ntpServer"));
    bool ntp_enabled = cJSON_IsTrue(cJSON_GetObjectItem(root, "ntpEnabled"));
    bool ntp_from_dhcp = cJSON_IsTrue(cJSON_GetObjectItem(root, "ntpFromDhcp"));
    char *timezone = cJSON_GetStringValue(cJSON_GetObjectItem(root, "timezone"));

    date_time_set_ntp_config(ntp_enabled, ntp_server, ntp_from_dhcp);
    return date_time_set_timezone(timezone);
}

cJSON *json_get_state(void)
{
    cJSON *root = cJSON_CreateObject();

    cJSON_AddStringToObject(root, "state", evse_state_to_str(evse_get_state()));
    cJSON_AddBoolToObject(root, "available", evse_is_available());
    cJSON_AddBoolToObject(root, "enabled", evse_is_enabled());
    cJSON_AddBoolToObject(root, "pendingAuth", evse_is_pending_auth());
    cJSON_AddBoolToObject(root, "limitReached", evse_is_limit_reached());

    uint32_t error = evse_error_get_bits();
    if (error == 0)
    {
        cJSON_AddNullToObject(root, "errors");
    }
    else
    {
        cJSON *errors = cJSON_CreateArray();
        if (error & EVSE_ERR_PILOT_FAULT_BIT)
        {
            cJSON_AddItemToArray(errors, cJSON_CreateString("pilot_fault"));
        }
        if (error & EVSE_ERR_DIODE_SHORT_BIT)
        {
            cJSON_AddItemToArray(errors, cJSON_CreateString("diode_short"));
        }
        if (error & EVSE_ERR_LOCK_FAULT_BIT)
        {
            cJSON_AddItemToArray(errors, cJSON_CreateString("lock_fault"));
        }
        if (error & EVSE_ERR_UNLOCK_FAULT_BIT)
        {
            cJSON_AddItemToArray(errors, cJSON_CreateString("unlock_fault"));
        }
        if (error & EVSE_ERR_RCM_TRIGGERED_BIT)
        {
            cJSON_AddItemToArray(errors, cJSON_CreateString("rcm_triggered"));
        }
        if (error & EVSE_ERR_RCM_SELFTEST_FAULT_BIT)
        {
            cJSON_AddItemToArray(errors, cJSON_CreateString("rcm_selftest_fault"));
        }
        if (error & EVSE_ERR_TEMPERATURE_HIGH_BIT)
        {
            cJSON_AddItemToArray(errors, cJSON_CreateString("temperature_high"));
        }
        if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT)
        {
            cJSON_AddItemToArray(errors, cJSON_CreateString("temperature_fault"));
        }
        cJSON_AddItemToObject(root, "errors", errors);
    }

    cJSON_AddNumberToObject(root, "sessionTime", energy_meter_get_session_time());
    cJSON_AddNumberToObject(root, "chargingTime", energy_meter_get_charging_time());
    cJSON_AddNumberToObject(root, "consumption", energy_meter_get_consumption());
    cJSON_AddNumberToObject(root, "power", energy_meter_get_power());
    float values[3];
    energy_meter_get_voltage(values);
    cJSON_AddItemToObject(root, "voltage", cJSON_CreateFloatArray(values, 3));
    energy_meter_get_current(values);
    cJSON_AddItemToObject(root, "current", cJSON_CreateFloatArray(values, 3));

    return root;
}

cJSON *json_get_info(void)
{
    cJSON *root = cJSON_CreateObject();

    cJSON_AddNumberToObject(root, "uptime", esp_timer_get_time() / 1000000);
    const esp_app_desc_t *app_desc = esp_app_get_description();
    cJSON_AddStringToObject(root, "appVersion", app_desc->version);
    cJSON_AddStringToObject(root, "appDate", app_desc->date);
    cJSON_AddStringToObject(root, "appTime", app_desc->time);
    cJSON_AddStringToObject(root, "idfVersion", app_desc->idf_ver);
    cJSON_AddStringToObject(root, "chip", CONFIG_IDF_TARGET);
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    cJSON_AddNumberToObject(root, "chipCores", chip_info.cores);
    chip_info.revision = 301;
    cJSON_AddNumberToObject(root, "chipRevision", chip_info.revision / 100);
    multi_heap_info_t heap_info;
    heap_caps_get_info(&heap_info, MALLOC_CAP_INTERNAL);
    cJSON_AddNumberToObject(root, "heapSize", heap_info.total_allocated_bytes);
    cJSON_AddNumberToObject(root, "maxHeapSize", heap_info.total_free_bytes + heap_info.total_allocated_bytes);
    uint8_t mac[6];
    char str[32];
    esp_wifi_get_mac(ESP_IF_WIFI_STA, mac);
    sprintf(str, MACSTR, MAC2STR(mac));
    cJSON_AddStringToObject(root, "mac", str);
    esp_netif_ip_info_t ip_info;
    esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info);
    esp_ip4addr_ntoa(&ip_info.ip, str, sizeof(str));
    cJSON_AddStringToObject(root, "ip", str);
    esp_wifi_get_mac(ESP_IF_WIFI_AP, mac);
    sprintf(str, MACSTR, MAC2STR(mac));
    cJSON_AddStringToObject(root, "macAp", str);
    esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
    esp_ip4addr_ntoa(&ip_info.ip, str, sizeof(str));
    cJSON_AddStringToObject(root, "ipAp", str);
    cJSON_AddNumberToObject(root, "temperatureSensorCount", temp_sensor_get_count());
    cJSON_AddNumberToObject(root, "temperatureLow", temp_sensor_get_low() / 100.0);
    cJSON_AddNumberToObject(root, "temperatureHigh", temp_sensor_get_high() / 100.0);
    return root;
}

static const char *serial_to_str(board_config_serial_t serial)
{
    switch (serial)
    {
    case BOARD_CONFIG_SERIAL_UART:
        return "uart";
    case BOARD_CONFIG_SERIAL_RS485:
        return "rs485";
    default:
        return "none";
    }
}

cJSON *json_get_board_config(void)
{
    cJSON *root = cJSON_CreateObject();

    cJSON_AddStringToObject(root, "deviceName", board_config.device_name);
    cJSON_AddBoolToObject(root, "socketLock", board_config.socket_lock);
    cJSON_AddBoolToObject(root, "proximity", board_config.proximity);
    cJSON_AddNumberToObject(root, "socketLockMinBreakTime", board_config.socket_lock_min_break_time);
    cJSON_AddBoolToObject(root, "rcm", board_config.rcm);
    cJSON_AddBoolToObject(root, "temperatureSensor", board_config.onewire && board_config.onewire_temp_sensor);
    switch (board_config.energy_meter)
    {
    case BOARD_CONFIG_ENERGY_METER_CUR:
        cJSON_AddStringToObject(root, "energyMeter", "cur");
        break;
    case BOARD_CONFIG_ENERGY_METER_CUR_VLT:
        cJSON_AddStringToObject(root, "energyMeter", "cur_vlt");
        break;
    default:
        cJSON_AddStringToObject(root, "energyMeter", "none");
    }
    cJSON_AddBoolToObject(root, "energyMeterThreePhases", board_config.energy_meter_three_phases);

    cJSON_AddStringToObject(root, "serial1", serial_to_str(board_config.serial_1));
    cJSON_AddStringToObject(root, "serial1Name", board_config.serial_1_name);
    cJSON_AddStringToObject(root, "serial2", serial_to_str(board_config.serial_2));
    cJSON_AddStringToObject(root, "serial2Name", board_config.serial_2_name);
#if SOC_UART_NUM > 2
    cJSON_AddStringToObject(root, "serial3", serial_to_str(board_config.serial_3));
    cJSON_AddStringToObject(root, "serial3Name", board_config.serial_3_name);
#else
    cJSON_AddStringToObject(root, "serial3", serial_to_str(BOARD_CONFIG_SERIAL_NONE));
    cJSON_AddStringToObject(root, "serial3Name", "");
#endif

    cJSON *aux = cJSON_CreateArray();
    if (board_config.aux_in_1)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_in_1_name));
    }
    if (board_config.aux_in_2)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_in_2_name));
    }
    if (board_config.aux_in_3)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_in_3_name));
    }
    if (board_config.aux_in_4)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_in_4_name));
    }
    cJSON_AddItemToObject(root, "auxIn", aux);

    aux = cJSON_CreateArray();
    if (board_config.aux_out_1)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_out_1_name));
    }
    if (board_config.aux_out_2)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_out_2_name));
    }
    if (board_config.aux_out_3)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_out_3_name));
    }
    if (board_config.aux_out_4)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_out_4_name));
    }
    cJSON_AddItemToObject(root, "auxOut", aux);

    aux = cJSON_CreateArray();
    if (board_config.aux_ain_1)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_ain_1_name));
    }
    if (board_config.aux_ain_2)
    {
        cJSON_AddItemToArray(aux, cJSON_CreateString(board_config.aux_ain_2_name));
    }
    cJSON_AddItemToObject(root, "auxAin", aux);

    return root;
}
