#include "loadbalancer.h"
#include "loadbalancer_events.h"
#include "esp_event.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "input_filter.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <string.h>
#include "meter_events.h"
#include "evse_events.h"
#include <math.h>

static const char *TAG = "loadbalancer";

// Configurable limits
#define MIN_CHARGING_CURRENT_LIMIT 6  // A
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
#define MIN_GRID_CURRENT_LIMIT 6      // A
#define MAX_GRID_CURRENT_LIMIT 100    // A

// Parameters
static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT;
static bool loadbalancer_enabled = false;

static float grid_current = 0.0f;
static float evse_current = 0.0f;
static input_filter_t grid_filter;
static input_filter_t evse_filter;

#define NVS_NAMESPACE "loadbalancing"
#define NVS_MAX_GRID_CURRENT "max_grid_curr"
#define NVS_LOADBALANCER_ENABLED "enabled"

// Reset filter helper
static void input_filter_reset(input_filter_t *filter)
{
    filter->value = 0.0f;
}

static void loadbalancer_meter_event_handler(void *handler_arg,
                                             esp_event_base_t base,
                                             int32_t id,
                                             void *event_data)
{
    if (id != METER_EVENT_DATA_READY || event_data == NULL)
        return;

    const meter_event_data_t *evt = (const meter_event_data_t *)event_data;

    ESP_LOGI(TAG, "Received meter event from source: %s", evt->source);
    ESP_LOGI(TAG, "IRMS: [%.2f, %.2f, %.2f] A", evt->irms[0], evt->irms[1], evt->irms[2]);
    ESP_LOGI(TAG, "VRMS: [%.1f, %.1f, %.1f] V", evt->vrms[0], evt->vrms[1], evt->vrms[2]);
    ESP_LOGI(TAG, "Power: [W1=%d, W2=%d, W3=%d]", evt->watt[0], evt->watt[1], evt->watt[2]);
    ESP_LOGI(TAG, "Freq: %.2f Hz | PF: %.2f | Energy: %.3f kWh",
             evt->frequency, evt->power_factor, evt->total_energy);

    float max_irms = evt->irms[0];
    for (int i = 1; i < 3; ++i)
    {
        if (evt->irms[i] > max_irms)
        {
            max_irms = evt->irms[i];
        }
    }

    ESP_LOGI(TAG, "Max IRMS detected: %.2f A", max_irms);

    if (evt->source && strcmp(evt->source, "GRID") == 0)
    {
        grid_current = input_filter_update(&grid_filter, max_irms);
        ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current);
    }
    else if (evt->source && strcmp(evt->source, "EVSE") == 0)
    {
        evse_current = input_filter_update(&evse_filter, max_irms);
        ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current);
    }
    else
    {
        ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source);
    }
}

static void loadbalancer_evse_event_handler(void *handler_arg,
                                            esp_event_base_t base,
                                            int32_t id,
                                            void *event_data)
{
    const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data;

    ESP_LOGI(TAG, "EVSE state changed: %d", evt->state);

    switch (evt->state)
    {
    case EVSE_STATE_EVENT_IDLE:
        ESP_LOGI(TAG, "EVSE is IDLE - vehicle disconnected");
        break;

    case EVSE_STATE_EVENT_WAITING:
        ESP_LOGI(TAG, "EVSE is WAITING - connected but not charging");
        break;

    case EVSE_STATE_EVENT_CHARGING:
        ESP_LOGI(TAG, "EVSE is CHARGING - resetting filters");
        grid_current = 0.0f;
        evse_current = 0.0f;
        input_filter_reset(&grid_filter);
        input_filter_reset(&evse_filter);
        break;

    case EVSE_STATE_EVENT_FAULT:
        ESP_LOGW(TAG, "EVSE is in FAULT state - consider disabling load balancing");
        break;

    default:
        ESP_LOGW(TAG, "Unknown EVSE state: %d", evt->state);
        break;
    }
}

static esp_err_t loadbalancer_load_config()
{
    nvs_handle_t handle;
    esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
        return err;
    }

    bool needs_commit = false;
    uint8_t temp_u8;

    err = nvs_get_u8(handle, NVS_MAX_GRID_CURRENT, &temp_u8);
    if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT)
    {
        max_grid_current = temp_u8;
    }
    else
    {
        max_grid_current = MAX_GRID_CURRENT_LIMIT;
        nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, max_grid_current);
        ESP_LOGW(TAG, "max_grid_current invalid or missing, set to default: %d", max_grid_current);
        needs_commit = true;
    }

    err = nvs_get_u8(handle, NVS_LOADBALANCER_ENABLED, &temp_u8);
    if (err == ESP_OK && temp_u8 <= 1)
    {
        loadbalancer_enabled = (temp_u8 != 0);
    }
    else
    {
        loadbalancer_enabled = false;
        nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, 0);
        ESP_LOGW(TAG, "loadbalancer_enabled invalid or missing, set to false");
        needs_commit = true;
    }

    if (needs_commit)
    {
        nvs_commit(handle);
    }

    nvs_close(handle);
    return ESP_OK;
}

void loadbalancer_set_enabled(bool enabled)
{
    ESP_LOGI(TAG, "Setting load balancing to %d", enabled);
    nvs_handle_t handle;
    esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
        return;
    }

    err = nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, enabled ? 1 : 0);
    if (err == ESP_OK)
    {
        nvs_commit(handle);
        loadbalancer_enabled = enabled;
        ESP_LOGI(TAG, "Load balancing state saved");

        loadbalancer_state_event_t evt = {
            .enabled = enabled,
            .timestamp_us = esp_timer_get_time()};

        esp_event_post(LOADBALANCER_EVENTS,
                       LOADBALANCER_EVENT_STATE_CHANGED,
                       &evt,
                       sizeof(evt),
                       portMAX_DELAY);
    }
    else
    {
        ESP_LOGE(TAG, "Failed to save loadbalancer_enabled");
    }

    nvs_close(handle);
}

esp_err_t load_balancing_set_max_grid_current(uint8_t value)
{
    if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT)
    {
        ESP_LOGE(TAG, "Invalid grid current limit: %d", value);
        return ESP_ERR_INVALID_ARG;
    }

    nvs_handle_t handle;
    esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
        return err;
    }

    err = nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, value);
    if (err == ESP_OK)
    {
        nvs_commit(handle);
        max_grid_current = value;
        ESP_LOGI(TAG, "max_grid_current set to: %d", value);
    }
    else
    {
        ESP_LOGE(TAG, "Failed to save max_grid_current to NVS");
    }

    nvs_close(handle);
    return err;
}

uint8_t load_balancing_get_max_grid_current(void)
{
    return max_grid_current;
}

bool loadbalancer_is_enabled(void)
{
    return loadbalancer_enabled;
}

void loadbalancer_task(void *param)
{
    while (true)
    {
        if (!loadbalancer_enabled)
        {
            vTaskDelay(pdMS_TO_TICKS(5000));
            continue;
        }

        float available = max_grid_current - grid_current;

        if (available < 0.0f)
        {
            available = 0.0f;
        }
        else if (available < MIN_CHARGING_CURRENT_LIMIT)
        {
            available = MIN_CHARGING_CURRENT_LIMIT;
        }
        else if (available > max_grid_current)
        {
            available = max_grid_current;
        }

        ESP_LOGD(TAG, "Calculated available EVSE current: %.1f A", available);

        loadbalancer_charging_limit_event_t evt = {
            .limit = available,
            .timestamp_us = esp_timer_get_time()};

        esp_event_post(LOADBALANCER_EVENTS,
                       LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED,
                       &evt,
                       sizeof(evt),
                       portMAX_DELAY);

        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

void loadbalancer_init(void)
{
    ESP_LOGI(TAG, "Initializing load balancer");

    if (loadbalancer_load_config() != ESP_OK)
    {
        ESP_LOGW(TAG, "Failed to load/init config. Using in-memory defaults.");
    }

    input_filter_init(&grid_filter, 0.3f);
    input_filter_init(&evse_filter, 0.3f);

    if (xTaskCreate(loadbalancer_task, "loadbalancer", 4096, NULL, 4, NULL) != pdPASS)
    {
        ESP_LOGE(TAG, "Failed to create loadbalancer task");
    }

    loadbalancer_state_event_t evt = {
        .enabled = loadbalancer_enabled,
        .timestamp_us = esp_timer_get_time()};

    esp_event_post(LOADBALANCER_EVENTS,
                   LOADBALANCER_EVENT_INIT,
                   &evt,
                   sizeof(evt),
                   portMAX_DELAY);

    ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY,
                                               &loadbalancer_meter_event_handler, NULL));

    ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS,
                                               EVSE_EVENT_STATE_CHANGED,
                                               &loadbalancer_evse_event_handler,
                                               NULL));
}
