#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_event.h"

#include <inttypes.h> // para PRIu32

#include "led.h"
#include "board_config.h"
#include "evse_events.h"
#include "evse_state.h"
#include "ledc_driver.h"

#define BLOCK_TIME portMAX_DELAY

static const char *TAG = "led";

typedef struct
{
    bool present;          ///< LED existe nesta placa
    bool on : 1;           ///< estado lógico atual (ligado/desligado)
    uint16_t ontime;       ///< ms ligado
    uint16_t offtime;      ///< ms desligado
    TimerHandle_t timer;   ///< timer de piscar (para padrões que usam on/off)
    led_pattern_t pattern; ///< padrão atual
    uint8_t blink_count;   ///< reservado para padrões mais complexos
} led_t;

static led_t leds[LED_ID_MAX] = {0};

// ----------------------------
// Tabela de padrões (tempo on/off)
// ----------------------------

typedef struct
{
    uint16_t on_ms;
    uint16_t off_ms;
} led_timing_t;

// índice = led_pattern_t
static const led_timing_t led_pattern_table[] = {
    [LED_PATTERN_OFF] = {0, 0},
    [LED_PATTERN_ON] = {1, 0}, // 1ms só para cair na lógica "sempre ligado"
    [LED_PATTERN_BLINK] = {500, 500},
    [LED_PATTERN_BLINK_FAST] = {250, 250},
    [LED_PATTERN_BLINK_SLOW] = {500, 1500},
    [LED_PATTERN_CHARGING_EFFECT] = {2000, 1000},
    // Para BREATHING, o temporizador FreeRTOS NÃO é usado; tratamos via task de efeitos.
    // Mesmo assim, usamos (1,0) para marcar LED como "on" logicamente.
    [LED_PATTERN_BREATHING] = {1, 0},
};

#define LED_PATTERN_COUNT (sizeof(led_pattern_table) / sizeof(led_pattern_table[0]))

// ----------------------------
// Estado base + efeitos de sessão
// ----------------------------

typedef enum
{
    SESSION_EFFECT_NONE = 0,
    SESSION_EFFECT_START,
    SESSION_EFFECT_FINISH,
} led_session_effect_type_t;

static evse_state_event_t current_state_mode = EVSE_STATE_EVENT_IDLE;
static bool session_effect_active = false;
static TimerHandle_t session_effect_timer = NULL;
static led_session_effect_type_t session_effect_type = SESSION_EFFECT_NONE;
static uint8_t session_effect_phase = 0;

// Forwards
static void led_update_rgb_from_state(void);
static void led_effect_task(void *arg);
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data);
static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data);
static void session_effect_timer_cb(TimerHandle_t xTimer);
static void led_apply_state_mode(evse_state_event_t state);

// ----------------------------
// Timer de piscar (para padrões on/off)
// ----------------------------
static void led_timer_callback(TimerHandle_t xTimer)
{
    led_t *led = (led_t *)pvTimerGetTimerID(xTimer);
    led->on = !led->on;

    uint32_t next_time = led->on ? led->ontime : led->offtime;
    xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME);

    // Atualiza hardware (via LEDC).
    led_update_rgb_from_state();
}

// ----------------------------
// Atualiza hardware a partir do estado lógico dos LEDs
// (para padrões "normais": ON/OFF/BLINK/...)
// ----------------------------
static void led_update_rgb_from_state(void)
{
    uint32_t red = 0;
    uint32_t green = 0;
    uint32_t blue = 0;

    if (LED_ID_RED < LED_ID_MAX && leds[LED_ID_RED].present && leds[LED_ID_RED].on)
        red = 255;
    if (LED_ID_GREEN < LED_ID_MAX && leds[LED_ID_GREEN].present && leds[LED_ID_GREEN].on)
        green = 255;
    if (LED_ID_BLUE < LED_ID_MAX && leds[LED_ID_BLUE].present && leds[LED_ID_BLUE].on)
        blue = 255;

    ledc_set_rgb(red, green, blue);
}

// ----------------------------
// Task de efeitos (BREATHING)
// ----------------------------
static void led_effect_task(void *arg)
{
    // v = "intensidade" lógica 0–100
    uint32_t v = 0;
    bool up = true;

    const uint32_t v_min = 0;
    const uint32_t v_max = 100;
    const uint32_t step = 3;
    const TickType_t delay_breathe = pdMS_TO_TICKS(50); // velocidade da respiração
    const TickType_t delay_idle = pdMS_TO_TICKS(1000);  // quando não há LED em BREATHING

    for (;;)
    {
        // Verifica se algum LED está em BREATHING
        bool has_breath = false;
        led_id_t breath_id = LED_ID_MAX;

        for (int i = 0; i < LED_ID_MAX; ++i)
        {
            if (leds[i].present && leds[i].pattern == LED_PATTERN_BREATHING)
            {
                has_breath = true;
                breath_id = (led_id_t)i;
                break;
            }
        }

        if (has_breath)
        {
            // Usa o valor atual de v para calcular o brilho (0–255)
            uint32_t brightness = (v * 255U) / 100U;
            uint32_t r = 0, g = 0, b = 0;

            switch (breath_id)
            {
            case LED_ID_RED:
                r = brightness;
                break;
            case LED_ID_GREEN:
                g = brightness;
                break;
            case LED_ID_BLUE:
                b = brightness;
                break;
            default:
                // fallback: usa azul se algo estranho acontecer
                b = brightness;
                break;
            }

            // Aplica só um canal, sem misturar cores
            ledc_set_rgb(r, g, b);

            // Se estiver completamente apagado (v == 0),
            // queremos que fique 500 ms off antes de voltar a subir
            TickType_t delay = (v == v_min) ? pdMS_TO_TICKS(500) : delay_breathe;

            // Atualiza v para a próxima iteração (triângulo 0→100→0)
            if (up)
            {
                if (v + step >= v_max)
                {
                    v = v_max;
                    up = false;
                }
                else
                {
                    v += step;
                }
            }
            else
            {
                if (v <= v_min + step)
                {
                    v = v_min;
                    up = true;
                }
                else
                {
                    v -= step;
                }
            }

            vTaskDelay(delay);
        }
        else
        {
            // Ninguém em BREATHING: deixa padrões normais controlarem o LED
            led_update_rgb_from_state();
            vTaskDelay(delay_idle);

            // Opcional: quando sair de BREATHING e voltar mais tarde,
            // começa de novo a partir de apagado
            v = v_min;
            up = true;
        }
    }
}

// ----------------------------
// Aplica padrão em função do estado EVSE (base)
// GARANTINDO apenas 1 LED por estado
// ----------------------------
static void led_apply_state_mode(evse_state_event_t state)
{
    // Desliga padrões anteriores para todos os canais
    for (int i = 0; i < LED_ID_MAX; ++i)
    {
        led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
    }

    switch (state)
    {
    case EVSE_STATE_EVENT_IDLE:
        // IDLE → verde fixo (claro e visível)
        led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
        break;

    case EVSE_STATE_EVENT_WAITING:
        // WAITING → azul a piscar lento
        led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_SLOW);
        break;

    case EVSE_STATE_EVENT_CHARGING:
        // CHARGING → azul "breathing"
        led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BREATHING);
        break;

    case EVSE_STATE_EVENT_FAULT:
        // FAULT → vermelho a piscar rápido
        led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
        break;

    default:
        break;
    }

    led_update_rgb_from_state();
}

// ----------------------------
// Timer callback do efeito de sessão
// Efeitos usam sempre UM LED forte
// ----------------------------
static void session_effect_timer_cb(TimerHandle_t xTimer)
{
    switch (session_effect_type)
    {
    case SESSION_EFFECT_START:
        session_effect_phase++;
        switch (session_effect_phase)
        {
        case 1:
            // Fase 1: depois de flash sólido, passa a azul a piscar rápido
            for (int i = 0; i < LED_ID_MAX; ++i)
            {
                led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
            }
            led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
            led_update_rgb_from_state();

            // Mantém piscar rápido mais um bocado
            xTimerChangePeriod(session_effect_timer,
                               pdMS_TO_TICKS(5000),
                               BLOCK_TIME);
            xTimerStart(session_effect_timer, BLOCK_TIME);
            break;

        case 2:
        default:
            // Fim do efeito de START → volta ao estado base (tipicamente CHARGING)
            session_effect_active = false;
            session_effect_type = SESSION_EFFECT_NONE;
            session_effect_phase = 0;
            led_apply_state_mode(current_state_mode);
            break;
        }
        break;

    case SESSION_EFFECT_FINISH:
        session_effect_phase++;
        switch (session_effect_phase)
        {
        case 1:
            // Fase 1: depois de flash sólido, passa a azul a piscar rápido
            for (int i = 0; i < LED_ID_MAX; ++i)
            {
                led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
            }
            led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
            led_update_rgb_from_state();

            // Mantém piscar rápido mais tempo (destaque de fim de sessão)
            xTimerChangePeriod(session_effect_timer,
                               pdMS_TO_TICKS(5000),
                               BLOCK_TIME);
            xTimerStart(session_effect_timer, BLOCK_TIME);
            break;

        case 2:
        default:
            // Fim do efeito de FINISH → volta ao estado base (IDLE/WAITING)
            session_effect_active = false;
            session_effect_type = SESSION_EFFECT_NONE;
            session_effect_phase = 0;
            led_apply_state_mode(current_state_mode);
            break;
        }
        break;

    case SESSION_EFFECT_NONE:
    default:
        session_effect_active = false;
        session_effect_phase = 0;
        led_apply_state_mode(current_state_mode);
        break;
    }
}

// ----------------------------
// Event Handler: EVSE State
// ----------------------------
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
    {
        return;
    }

    const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;

    ESP_LOGD(TAG, "EVSE State Changed: state=%d", evt->state);

    // Atualiza o estado base
    current_state_mode = evt->state;

    // Se estiver a decorrer um efeito de sessão, não mexe agora nos LEDs.
    if (session_effect_active)
    {
        return;
    }

    led_apply_state_mode(current_state_mode);
}

// ----------------------------
// Event Handler: EVSE Session
// (efeitos de início/fim de sessão, 1 LED de cada vez)
// ----------------------------
static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    if (base != EVSE_EVENTS || id != EVSE_EVENT_SESSION || data == NULL)
    {
        return;
    }

    const evse_session_event_data_t *evt =
        (const evse_session_event_data_t *)data;

    ESP_LOGD(TAG,
             "EVSE Session Event: type=%d, id=%" PRIu32
             ", duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg=%" PRIu32 " W, current=%d",
             (int)evt->type,
             evt->session_id,
             evt->duration_s,
             evt->energy_wh,
             evt->avg_power_w,
             evt->is_current);

    // Marca que um efeito de sessão está ativo
    session_effect_active = true;
    session_effect_phase = 0;

    if (session_effect_timer)
    {
        xTimerStop(session_effect_timer, BLOCK_TIME);
    }

    // Apaga tudo antes de iniciar o efeito
    for (int i = 0; i < LED_ID_MAX; ++i)
    {
        led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
    }

    switch (evt->type)
    {
    case EVSE_SESSION_EVENT_STARTED:
        // Efeito de início:
        // Fase 0: azul sólido curto
        session_effect_type = SESSION_EFFECT_START;
        led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
        led_update_rgb_from_state();

        xTimerChangePeriod(session_effect_timer,
                           pdMS_TO_TICKS(300), // 0.3 s flash
                           BLOCK_TIME);
        xTimerStart(session_effect_timer, BLOCK_TIME);
        break;

    case EVSE_SESSION_EVENT_FINISHED:
        // Efeito de fim:
        // Fase 0: azul sólido curto
        session_effect_type = SESSION_EFFECT_FINISH;
        led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
        led_update_rgb_from_state();

        xTimerChangePeriod(session_effect_timer,
                           pdMS_TO_TICKS(300), // 0.3 s flash
                           BLOCK_TIME);
        xTimerStart(session_effect_timer, BLOCK_TIME);
        break;

    default:
        // Se for tipo desconhecido, desiste do efeito e volta ao estado base
        session_effect_active = false;
        session_effect_type = SESSION_EFFECT_NONE;
        session_effect_phase = 0;
        led_apply_state_mode(current_state_mode);
        break;
    }
}

// ----------------------------
// Inicialização
// ----------------------------

void led_init(void)
{
    // Marca quais LEDs existem de acordo com o board_config
    leds[LED_ID_GREEN].present = board_config.led_green;
    leds[LED_ID_BLUE].present = board_config.led_blue;
    leds[LED_ID_RED].present = board_config.led_red;

    // Inicializa LEDC com os GPIOs definidos na board
    esp_err_t err = ledc_init(board_config.led_red_gpio,
                              board_config.led_green_gpio,
                              board_config.led_blue_gpio);
    if (err != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to init LEDC: %s", esp_err_to_name(err));
    }

    // Regista handler de evento EVSE - STATE
    ESP_ERROR_CHECK(esp_event_handler_register(
        EVSE_EVENTS,
        EVSE_EVENT_STATE_CHANGED,
        evse_led_event_handler,
        NULL));

    // Regista handler de evento EVSE - SESSION
    ESP_ERROR_CHECK(esp_event_handler_register(
        EVSE_EVENTS,
        EVSE_EVENT_SESSION,
        evse_session_led_event_handler,
        NULL));

    // Cria task de efeitos (breathing)
    xTaskCreate(led_effect_task, "led_effect_task", 2048, NULL, 1, NULL);

    // Cria timer one-shot para efeitos de sessão
    session_effect_timer = xTimerCreate(
        "session_eff",
        pdMS_TO_TICKS(1000), // valor default; ajustado com xTimerChangePeriod
        pdFALSE,             // one-shot
        NULL,
        session_effect_timer_cb);

    ESP_LOGI(TAG, "LED system initialized");

    // Estado inicial: IDLE
    evse_state_event_data_t evt = {
        .state = EVSE_STATE_EVENT_IDLE};
    evse_led_event_handler(NULL, EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt);
}

// ----------------------------
// API Pública
// ----------------------------

void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
{
    if (led_id >= LED_ID_MAX)
    {
        return;
    }

    led_t *led = &leds[led_id];
    if (!led->present)
    {
        return;
    }

    if (led->ontime == ontime && led->offtime == offtime)
    {
        return;
    }

    if (led->timer)
    {
        xTimerStop(led->timer, BLOCK_TIME);
    }

    led->ontime = ontime;
    led->offtime = offtime;

    if (ontime == 0)
    {
        // sempre desligado
        led->on = false;
    }
    else if (offtime == 0)
    {
        // sempre ligado
        led->on = true;
    }
    else
    {
        // pisca
        led->on = true;

        if (!led->timer)
        {
            // nome só para debug; opcional
            led->timer = xTimerCreate("led_timer",
                                      pdMS_TO_TICKS(ontime),
                                      pdFALSE,
                                      (void *)led,
                                      led_timer_callback);
        }

        if (led->timer)
        {
            xTimerStart(led->timer, BLOCK_TIME);
        }
    }

    // Atualiza hardware (para estados sem BREATHING)
    led_update_rgb_from_state();
}

void led_apply_pattern(led_id_t id, led_pattern_t pattern)
{
    if (id >= LED_ID_MAX)
    {
        return;
    }

    led_t *led = &leds[id];
    if (!led->present)
    {
        return;
    }

    if ((unsigned)pattern >= LED_PATTERN_COUNT)
    {
        ESP_LOGW(TAG, "Invalid LED pattern %d", pattern);
        return;
    }

    if (led->pattern == pattern)
    {
        return;
    }

    if (led->timer)
    {
        xTimerStop(led->timer, BLOCK_TIME);
    }

    led->pattern = pattern;
    led->blink_count = 0;

    const led_timing_t *cfg = &led_pattern_table[pattern];
    led_set_state(id, cfg->on_ms, cfg->off_ms);

    // led_set_state já chama led_update_rgb_from_state()
}
