// === Início de: main/main.c ===
#include <string.h>
#include <stdbool.h>
#include <inttypes.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

#include "esp_log.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_spiffs.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/gpio.h"

#include "network.h"
#include "board_config.h"
#include "rest_main.h"

#include "peripherals.h"
#include "protocols.h"
#include "evse_manager.h"
#include "evse_core.h"
#include "auth.h"
#include "loadbalancer.h"
#include "meter_manager.h"
#include "buzzer.h"
#include "evse_link.h"
#include "ocpp.h"
#include "led.h"
#include "scheduler.h"
#include "storage_service.h"
#include "time_service.h"

/*
 * Modos de controlo do AP:
 *
 * APP_AP_CONTROL_BUTTON:
 *   - Usa botão físico.
 *   - Pressão curta: inicia AP.
 *   - Pressão longa: apaga NVS e reinicia.
 *
 * APP_AP_CONTROL_BOOT_WINDOW:
 *   - Não usa botão físico.
 *   - Inicia AP automaticamente no arranque.
 *   - Mantém AP ativo durante os primeiros 3 minutos.
 *   - Depois desliga AP e volta ao modo STA.
 */
#define APP_AP_CONTROL_BUTTON 0
#define APP_AP_CONTROL_BOOT_WINDOW 1

/*
 * Escolher aqui o modo pretendido:
 *
 * Para placa COM botão:
 * #define APP_AP_CONTROL_MODE APP_AP_CONTROL_BUTTON
 *
 * Para placa SEM botão:
 * #define APP_AP_CONTROL_MODE APP_AP_CONTROL_BOOT_WINDOW
 */
#define APP_AP_CONTROL_MODE APP_AP_CONTROL_BOOT_WINDOW

#define AP_BOOT_WINDOW_MS 180000UL // 3 minutos
#define AP_CONNECTION_TIMEOUT AP_BOOT_WINDOW_MS

#define RESET_HOLD_TIME 30000UL // ms
#define DEBOUNCE_TIME_MS 50UL

#define PRESS_BIT BIT0
#define RELEASED_BIT BIT1

static const char *TAG = "app_main";

static TaskHandle_t user_input_task = NULL;
static TickType_t press_tick = 0;
static volatile TickType_t last_interrupt_tick = 0;
static bool pressed = false;

// Spinlock para garantir debounce seguro na ISR
static portMUX_TYPE button_spinlock = portMUX_INITIALIZER_UNLOCKED;

//
// File system (SPIFFS) init and info
//
static void fs_info(esp_vfs_spiffs_conf_t *conf)
{
    size_t total = 0, used = 0;
    esp_err_t ret = esp_spiffs_info(conf->partition_label, &total, &used);
    if (ret == ESP_OK)
    {
        ESP_LOGI(TAG, "Partition %s: total: %d, used: %d",
                 conf->partition_label, (int)total, (int)used);
    }
    else
    {
        ESP_LOGE(TAG, "Failed to get SPIFFS info (%s): %s",
                 conf->partition_label, esp_err_to_name(ret));
    }
}

static void fs_init(void)
{
    esp_vfs_spiffs_conf_t cfg_conf = {
        .base_path = "/cfg",
        .partition_label = "cfg",
        .max_files = 1,
        .format_if_mount_failed = false};

    esp_vfs_spiffs_conf_t data_conf = {
        .base_path = "/data",
        .partition_label = "data",
        .max_files = 5,
        .format_if_mount_failed = true};

    ESP_ERROR_CHECK(esp_vfs_spiffs_register(&cfg_conf));
    ESP_ERROR_CHECK(esp_vfs_spiffs_register(&data_conf));

    fs_info(&cfg_conf);
    fs_info(&data_conf);
}

//
// Wi-Fi event monitoring task
//
static void wifi_event_task_func(void *param)
{
    (void)param;
    EventBits_t mode_bits;

    for (;;)
    {
        mode_bits = xEventGroupWaitBits(
            wifi_event_group,
            WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

        if (mode_bits & WIFI_AP_MODE_BIT)
        {
            if (xEventGroupWaitBits(
                    wifi_event_group,
                    WIFI_AP_CONNECTED_BIT,
                    pdFALSE,
                    pdFALSE,
                    pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) &
                WIFI_AP_CONNECTED_BIT)
            {
                // Espera sair do AP
                xEventGroupWaitBits(
                    wifi_event_group,
                    WIFI_AP_DISCONNECTED_BIT,
                    pdFALSE,
                    pdFALSE,
                    portMAX_DELAY);
            }
            else
            {
                if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)
                {
                    // Timeout sem cliente ligado
                    // O desligar do AP no modo sem botão é feito pela boot_ap_task_func().
                    // No modo com botão, mantemos o comportamento antigo.
                    ESP_LOGW(TAG, "AP timeout sem conexões");
                }
            }
        }
        else if (mode_bits & WIFI_STA_MODE_BIT)
        {
            // Apenas aguarda desconexão STA
            xEventGroupWaitBits(
                wifi_event_group,
                WIFI_STA_DISCONNECTED_BIT,
                pdFALSE,
                pdFALSE,
                portMAX_DELAY);
        }
    }
}

//
// Button press handler (short press => AP)
//
static void handle_button_press(void)
{
    // Pode ser chamado cedo demais se a ordem de init mudar
    if (wifi_event_group == NULL)
    {
        ESP_LOGW(TAG, "Wi-Fi ainda não inicializado, ignorando botão Wi-Fi");
        return;
    }

    if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT))
    {
        ESP_LOGI(TAG, "Starting Wi-Fi AP mode (short press)");
        wifi_ap_start();
    }
    else
    {
        ESP_LOGI(TAG, "Wi-Fi AP mode já está ativo");
    }
}

//
// Task para lidar com notificações de botão (PRESS / RELEASE)
//
static void user_input_task_func(void *param)
{
    (void)param;
    uint32_t notification;

    for (;;)
    {
        if (xTaskNotifyWait(
                0,
                UINT32_MAX,
                &notification,
                portMAX_DELAY))
        {
            if (notification & PRESS_BIT)
            {
                press_tick = xTaskGetTickCount();
                pressed = true;
                ESP_LOGI(TAG, "Button Pressed");
                // Decisão short/long é feita na soltura
            }

            if ((notification & RELEASED_BIT) && pressed)
            {
                pressed = false;

                TickType_t held_ticks = xTaskGetTickCount() - press_tick;
                uint32_t held_ms = pdTICKS_TO_MS(held_ticks);

                ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)held_ms);

                if (held_ms >= RESET_HOLD_TIME)
                {
                    ESP_LOGW(TAG, "Long press: erasing NVS + reboot");

                    esp_err_t ret = nvs_flash_erase();
                    if (ret != ESP_OK)
                    {
                        ESP_LOGE(TAG, "Erro ao apagar NVS: %s", esp_err_to_name(ret));
                    }

                    esp_restart();
                }
                else
                {
                    // Short press: apenas habilita modo AP
                    handle_button_press();
                }
            }
        }
    }
}

//
// ISR para GPIO do botão ativo em nível baixo
//
static void IRAM_ATTR button_isr_handler(void *arg)
{
    (void)arg;
    BaseType_t higher_task_woken = pdFALSE;
    TickType_t now = xTaskGetTickCountFromISR();

    portENTER_CRITICAL_ISR(&button_spinlock);

    if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS))
    {
        portEXIT_CRITICAL_ISR(&button_spinlock);
        return;
    }

    last_interrupt_tick = now;

    if (user_input_task == NULL)
    {
        portEXIT_CRITICAL_ISR(&button_spinlock);
        return;
    }

    int level = gpio_get_level(board_config.button_wifi_gpio);
    uint32_t bits = (level == 0) ? PRESS_BIT : RELEASED_BIT;

    xTaskNotifyFromISR(
        user_input_task,
        bits,
        eSetBits,
        &higher_task_woken);

    portEXIT_CRITICAL_ISR(&button_spinlock);

    if (higher_task_woken)
    {
        portYIELD_FROM_ISR();
    }
}

static void button_init(void)
{
    gpio_config_t conf = {
        .pin_bit_mask = BIT64(board_config.button_wifi_gpio),
        .mode = GPIO_MODE_INPUT,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .pull_up_en = GPIO_PULLUP_ENABLE,
        .intr_type = GPIO_INTR_ANYEDGE};

    ESP_ERROR_CHECK(gpio_config(&conf));
    ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.button_wifi_gpio, button_isr_handler, NULL));
}

//
// AP automático no arranque para placas sem botão
//
static void boot_ap_task_func(void *param)
{
    (void)param;

    if (wifi_event_group == NULL)
    {
        ESP_LOGW(TAG, "Wi-Fi ainda não inicializado, não é possível iniciar AP");
        vTaskDelete(NULL);
        return;
    }

    ESP_LOGI(TAG, "Modo sem botão: iniciar AP automaticamente durante 3 minutos");

    wifi_ap_start();

    vTaskDelay(pdMS_TO_TICKS(AP_BOOT_WINDOW_MS));

    EventBits_t bits = xEventGroupGetBits(wifi_event_group);

    if (bits & WIFI_AP_MODE_BIT)
    {
        ESP_LOGI(TAG, "Modo sem botão: janela de AP terminou, a desligar AP");
        wifi_ap_stop();
    }
    else
    {
        ESP_LOGI(TAG, "Modo sem botão: AP já não estava ativo");
    }

    vTaskDelete(NULL);
}

//
// Inicialização dos módulos do sistema
//
static void init_modules(void)
{
    ESP_ERROR_CHECK(storage_service_init());

    peripherals_init();
    wifi_ini(); // garante wifi_event_group inicializado
    buzzer_init();
    led_init();
    ESP_ERROR_CHECK(rest_server_init("/data"));

    evse_manager_init();
    evse_init();
    auth_init();
    meter_manager_init();
    meter_manager_start();
    ocpp_start();
    time_service_init();
    scheduler_init();
    protocols_init();
    loadbalancer_init();
    evse_link_init();
}

//
// Função principal do firmware
//
void app_main(void)
{
    esp_reset_reason_t reason = esp_reset_reason();
    ESP_LOGI(TAG, "Reset reason: %d", reason);

    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_LOGW(TAG, "Erasing NVS flash");
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    fs_init();

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    board_config_load();

    /*
    * O serviço GPIO ISR tem de estar instalado antes de qualquer módulo
    * chamar gpio_isr_handler_add().
    *
    * O RFID/Wiegand usa interrupções GPIO durante auth_init(),
    * que é chamado dentro de init_modules().
    */
    esp_err_t gpio_isr_ret = gpio_install_isr_service(0);
    if (gpio_isr_ret != ESP_OK && gpio_isr_ret != ESP_ERR_INVALID_STATE)
    {
        ESP_ERROR_CHECK(gpio_isr_ret);
    }


    // 1) Inicia todos os módulos, incluindo Wi-Fi, EVSE, etc.
    init_modules();

    BaseType_t rc;

#if APP_AP_CONTROL_MODE == APP_AP_CONTROL_BUTTON

    ESP_LOGI(TAG, "Modo AP por botão ativo");

    // 2) Cria a task que recebe notificações do botão
    rc = xTaskCreate(
        user_input_task_func,
        "user_input_task",
        4 * 1024,
        NULL,
        3,
        &user_input_task);
    configASSERT(rc == pdPASS);

    // 3) Agora é seguro registrar ISR do botão
    button_init();

#elif APP_AP_CONTROL_MODE == APP_AP_CONTROL_BOOT_WINDOW

    ESP_LOGI(TAG, "Modo AP automático no arranque ativo");

    // 2) Inicia AP automaticamente durante os primeiros 3 minutos
    rc = xTaskCreate(
        boot_ap_task_func,
        "boot_ap_task",
        4 * 1024,
        NULL,
        3,
        NULL);
    configASSERT(rc == pdPASS);

#else
#error "APP_AP_CONTROL_MODE inválido"
#endif

    // 4) Task auxiliar para eventos Wi-Fi
    rc = xTaskCreate(
        wifi_event_task_func,
        "wifi_event_task",
        8 * 1024,
        NULL,
        3,
        NULL);
    configASSERT(rc == pdPASS);
}

// === Fim de: main/main.c ===
