#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_event.h"
#include "mqtt_client.h"
#include "nvs.h"
#include "mqtt.h"
#include "json.h"
#include "board_config.h"
#include "timeout_utils.h"

#define NVS_NAMESPACE       "mqtt"
#define NVS_ENABLED         "enabled"
#define NVS_SERVER          "server"
#define NVS_BASE_TOPIC      "base_topic"
#define NVS_USER            "user"
#define NVS_PASSWORD        "password"
#define NVS_PERIODICITY     "periodicity"

static const char* TAG = "mqtt";
static TaskHandle_t client_task = NULL;
static esp_mqtt_client_handle_t client = NULL;
static uint16_t periodicity = 30;

static esp_err_t open_mqtt_nvs(nvs_handle_t *handle) {
    return nvs_open(NVS_NAMESPACE, NVS_READWRITE, handle);
}

static void subcribe_topics(void)
{
    ESP_LOGI(TAG, "[MQTT] Subscribing to topics");

    char base[32];
    mqtt_get_base_topic(base);

    char topic[64];

    snprintf(topic, sizeof(topic), "%s/request/#", base);
    esp_mqtt_client_subscribe(client, topic, 0);
    ESP_LOGI(TAG, "  subscribed: %s", topic);

    snprintf(topic, sizeof(topic), "%s/set/config/#", base);
    esp_mqtt_client_subscribe(client, topic, 0);
    ESP_LOGI(TAG, "  subscribed: %s", topic);

    snprintf(topic, sizeof(topic), "%s/enable", base);
    esp_mqtt_client_subscribe(client, topic, 0);
    ESP_LOGI(TAG, "  subscribed: %s", topic);
}

static void publish_message(const char* topic, cJSON* root)
{
    char target_topic[64];
    mqtt_get_base_topic(target_topic);
    strncat(target_topic, topic, sizeof(target_topic) - strlen(target_topic) - 1);

    const char* json = cJSON_PrintUnformatted(root);
    esp_mqtt_client_publish(client, target_topic, json, 0, 1, 0);
    free((void*)json);
}


static void handle_message(const char* topic, const char* data)
{
    char base_topic[32];
    mqtt_get_base_topic(base_topic);

    ESP_LOGI(TAG, "Topic: %s", topic);
    ESP_LOGI(TAG, "data: %s", data);
    ESP_LOGI(TAG, "base_topic: %s", base_topic);

    if (strncmp(topic, base_topic, strlen(base_topic)) == 0) {
        const char* sub_topic = &topic[strlen(base_topic)];

        ESP_LOGI(TAG, "Sub_topic: %s", sub_topic);

        if (strcmp(sub_topic, "/request/config/evse") == 0) {
            cJSON* root = json_get_evse_config();
            publish_message("/response/config/evse", root);
            cJSON_Delete(root);
        } else if (strcmp(sub_topic, "/request/config/wifi") == 0) {
            //cJSON* root = json_get_wifi_config();
            //publish_message("/response/config/wifi", root);
            //cJSON_Delete(root);
        } else if (strcmp(sub_topic, "/request/config/mqtt") == 0) {
            cJSON* root = json_get_mqtt_config();
            publish_message("/response/config/mqtt", root);
            cJSON_Delete(root);
        } else if (strcmp(sub_topic, "/request/boardConfig") == 0) {
            cJSON* root = json_get_board_config();
            publish_message("/response/boardConfig", root);
            cJSON_Delete(root);
        } else if (strcmp(sub_topic, "/request/info") == 0) {
            cJSON* root = json_get_info();
            publish_message("/response/info", root);
            cJSON_Delete(root);
        } else if (strcmp(sub_topic, "/request/restart") == 0) {
            timeout_restart();
        } else if (strcmp(sub_topic, "/set/config/evse") == 0) {
            cJSON* root = cJSON_Parse(data);
            json_set_evse_config(root);
            cJSON_Delete(root);
        } else if (strcmp(sub_topic, "/set/config/wifi") == 0) {
            //cJSON* root = cJSON_Parse(data);
            //json_set_wifi_config(root, true);
            //cJSON_Delete(root);
        } else if (strcmp(sub_topic, "/set/config/mqtt") == 0) {
            cJSON* root = cJSON_Parse(data);
            json_set_mqtt_config(root);
            cJSON_Delete(root);
        } 
    }
}

static void event_handler(void* handler_args, esp_event_base_t base, int32_t event_id, void* event_data)
{
    esp_mqtt_event_handle_t event = event_data;

    switch (event_id) {
        case MQTT_EVENT_CONNECTED:
            ESP_LOGI(TAG, "[MQTT] Connected to broker");
            if (client_task) vTaskResume(client_task);
            subcribe_topics();
            break;

        case MQTT_EVENT_DATA: {
            char topic[64] = {0};
            char data[256] = {0};

            int tlen = MIN(event->topic_len, sizeof(topic) - 1);
            int dlen = MIN(event->data_len, sizeof(data) - 1);

            memcpy(topic, event->topic, tlen);
            memcpy(data, event->data, dlen);

            handle_message(topic, data);
            break;
        }
        default:
            break;
    }
}

static void client_task_func(void* param)
{
    while (true) {
        if (!client) vTaskSuspend(NULL);
        cJSON* root = json_get_state();
        publish_message("/state", root);
        cJSON_Delete(root);
        vTaskDelay(pdMS_TO_TICKS(periodicity * 1000));
    }
}

static void client_start(void)
{
    char server[64], user[32], password[64];
    mqtt_get_server(server);
    mqtt_get_user(user);
    mqtt_get_password(password);

    esp_mqtt_client_config_t cfg = {
        .broker.address.uri = server,
        .credentials.username = user,
        .credentials.authentication.password = password
    };

    if (!client) {
        client = esp_mqtt_client_init(&cfg);
        esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, event_handler, client);
        esp_mqtt_client_start(client);
    }
}

static void client_stop(void)
{
    if (client) {
        esp_mqtt_client_destroy(client);
        client = NULL;
    }
}

void mqtt_init(void)
{
    nvs_handle_t handle;
    if (open_mqtt_nvs(&handle) == ESP_OK) {
        nvs_get_u16(handle, NVS_PERIODICITY, &periodicity);
        nvs_close(handle);
    }

    esp_register_shutdown_handler(&client_stop);
    xTaskCreate(client_task_func, "mqtt_client_task", 3 * 1024, NULL, 5, &client_task);

    if (mqtt_get_enabled()) {
        client_start();
    }
}

esp_err_t mqtt_set_config(bool enabled, const char* server, const char* base_topic, const char* user, const char* password, uint16_t _periodicity)
{
    nvs_handle_t handle;
    if (open_mqtt_nvs(&handle) != ESP_OK) return ESP_ERR_INVALID_STATE;

    char full_server[64];
    if (server && strncmp(server, "mqtt://", 7) != 0 && strncmp(server, "tcp://", 6) != 0) {
        snprintf(full_server, sizeof(full_server), "mqtt://%s", server);
        server = full_server;
    }

    if (enabled) {
        if (!server || !*server) return ESP_ERR_INVALID_ARG;
        if (!base_topic || !*base_topic) return ESP_ERR_INVALID_ARG;
        if (_periodicity == 0) return ESP_ERR_INVALID_ARG;
    }

    if (server) nvs_set_str(handle, NVS_SERVER, server);
    if (base_topic) nvs_set_str(handle, NVS_BASE_TOPIC, base_topic);
    if (user) nvs_set_str(handle, NVS_USER, user);
    if (password) nvs_set_str(handle, NVS_PASSWORD, password);
    nvs_set_u8(handle, NVS_ENABLED, enabled);
    nvs_set_u16(handle, NVS_PERIODICITY, _periodicity);

    periodicity = _periodicity;

    esp_err_t err = nvs_commit(handle);
    nvs_close(handle);

    if (err != ESP_OK) return err;

    if (enabled) {
        client_start();
    } else {
        client_stop();
    }

    return ESP_OK;
}

bool mqtt_get_enabled(void)
{
    nvs_handle_t handle;
    uint8_t val = false;
    if (open_mqtt_nvs(&handle) == ESP_OK) {
        nvs_get_u8(handle, NVS_ENABLED, &val);
        nvs_close(handle);
    }
    return val;
}

void mqtt_get_server(char* value)
{
    if (!value) return;
    value[0] = '\0';
    nvs_handle_t handle;
    if (open_mqtt_nvs(&handle) == ESP_OK) {
        size_t len = 64;
        nvs_get_str(handle, NVS_SERVER, value, &len);
        nvs_close(handle);
    }
}

void mqtt_get_base_topic(char* value)
{
    if (!value) return;
    value[0] = '\0';
    nvs_handle_t handle;
    if (open_mqtt_nvs(&handle) == ESP_OK) {
        size_t len = 32;
        nvs_get_str(handle, NVS_BASE_TOPIC, value, &len);
        nvs_close(handle);
    }
}

void mqtt_get_user(char* value)
{
    if (!value) return;
    value[0] = '\0';
    nvs_handle_t handle;
    if (open_mqtt_nvs(&handle) == ESP_OK) {
        size_t len = 32;
        nvs_get_str(handle, NVS_USER, value, &len);
        nvs_close(handle);
    }
}

void mqtt_get_password(char* value)
{
    if (!value) return;
    value[0] = '\0';
    nvs_handle_t handle;
    if (open_mqtt_nvs(&handle) == ESP_OK) {
        size_t len = 64;
        nvs_get_str(handle, NVS_PASSWORD, value, &len);
        nvs_close(handle);
    }
}

uint16_t mqtt_get_periodicity(void)
{
    return periodicity;
}