#include "user_app.h"
#include "dashboard_ui.h"
#include "ws_client.h"
#include "esp_wifi_bsp.h"
#include "wifi_config.h"
#include "i2c_bsp.h"
#include "i2c_equipment.h"
#include "adc_bsp.h"
#include "button_bsp.h"
#include "codec_bsp.h"
#include "alert.h"
#include "audio_client.h"
#include "lvgl_bsp.h"

#include <esp_log.h>
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <string.h>

static const char *TAG = "user_app";

/* Hardware objects */
static I2cMasterBus *s_i2c = nullptr;
static Shtc3Port *s_shtc3 = nullptr;
static CodecPort *s_codec = nullptr;

/* Forward declarations */
static void sensor_task(void *arg);
static void button_task(void *arg);
static void ws_data_cb(const pi_stats_t *stats);
static void ws_state_cb(ws_state_t state);

void UserApp_AppInit(void)
{
    ESP_LOGI(TAG, "Initializing hardware...");

    /* I2C bus (SCL=14, SDA=13, port 0) */
    s_i2c = new I2cMasterBus(14, 13, 0);

    /* SHTC3 temperature/humidity sensor */
    s_shtc3 = new Shtc3Port(*s_i2c);
    ESP_LOGI(TAG, "SHTC3 ID: 0x%04X", s_shtc3->Shtc3_GetShtc3Id());

    /* RTC */
    Rtc_Setup(s_i2c, 0x51);

    /* ADC for battery */
    Adc_PortInit();

    /* Buttons */
    Custom_ButtonInit();

    /* Codec */
    s_codec = new CodecPort(*s_i2c, "S3_RLCD_4_2");

    /* Alert system */
    alert_init();
    alert_set_codec(s_codec);

    /* Audio streaming client */
    audio_client_init(AUDIO_SERVER_URI, s_codec);

    /* WebSocket client init (not started yet) */
    ws_client_init(WS_SERVER_URI);
    ws_client_set_data_callback(ws_data_cb);
    ws_client_set_state_callback(ws_state_cb);

    ESP_LOGI(TAG, "Hardware init complete");
}

void UserApp_UiInit(void)
{
    dashboard_ui_create();
    ESP_LOGI(TAG, "Dashboard UI created");
}

void UserApp_TaskInit(void)
{
    ESP_LOGI(TAG, "Starting background tasks...");

    /* Start WebSocket client */
    ws_client_start();

    /* Sensor polling task - Core 1, 4KB stack */
    TaskHandle_t sensor_handle = NULL;
    xTaskCreatePinnedToCore(sensor_task, "sensor", 4 * 1024, NULL, 3, &sensor_handle, 1);

    /* Start audio streaming client, notify sensor task on new images */
    audio_client_set_image_notify_task(sensor_handle);
    audio_client_start();

    /* Button handling task - Core 1 */
    xTaskCreatePinnedToCore(button_task, "button", 2 * 1024, NULL, 2, NULL, 1);

    ESP_LOGI(TAG, "All tasks started");
}

/* ---------- WebSocket callbacks ---------- */

static void rtc_sync_if_needed(const pi_stats_t *stats)
{
    if (!stats->time_valid) return;

    rtcTimeStruct_t rtc = {};
    Rtc_GetTime(&rtc);

    /* Convert both to seconds-since-midnight for comparison */
    int pi_secs  = stats->time_hour * 3600 + stats->time_minute * 60 + stats->time_second;
    int rtc_secs = rtc.hour * 3600 + rtc.minute * 60 + rtc.second;
    int delta = pi_secs - rtc_secs;
    if (delta < 0) delta = -delta;

    /* Also check date mismatch as an immediate trigger */
    bool date_mismatch = (rtc.year != stats->time_year ||
                          rtc.month != stats->time_month ||
                          rtc.day != stats->time_day);

    if (date_mismatch || delta > 60) {
        Rtc_SetTime(stats->time_year, stats->time_month, stats->time_day,
                    stats->time_hour, stats->time_minute, stats->time_second);
        ESP_LOGI(TAG, "RTC synced from Pi: %04d-%02d-%02d %02d:%02d:%02d (drift: %ds)",
                 stats->time_year, stats->time_month, stats->time_day,
                 stats->time_hour, stats->time_minute, stats->time_second, delta);
    }
}

static void ws_data_cb(const pi_stats_t *stats)
{
    /* Check alert conditions */
    if (stats->cpu_temp > 80.0f) {
        alert_trigger(ALERT_HIGH_TEMP);
    }

    for (int i = 0; i < stats->service_count; i++) {
        if (stats->services[i].status != SVC_RUNNING) {
            alert_trigger(ALERT_SERVICE_DOWN);
            break;
        }
    }

    /* Sync RTC if Pi time drifts from board clock */
    rtc_sync_if_needed(stats);

    /* Update UI under LVGL lock */
    if (Lvgl_lock(100)) {
        dashboard_ui_update_stats(stats);

        char ip_buf[20];
        wifi_sta_get_ip_str(ip_buf, sizeof(ip_buf));
        dashboard_ui_update_connection(
            ws_client_get_state(),
            wifi_sta_is_connected(),
            ip_buf
        );

        Lvgl_unlock();
    }
}

static void ws_state_cb(ws_state_t state)
{
    ESP_LOGI(TAG, "WS state changed: %d", state);

    if (state == WS_STATE_DISCONNECTED || state == WS_STATE_ERROR) {
        alert_trigger(ALERT_WS_DISCONNECT);
    } else if (state == WS_STATE_CONNECTED) {
        alert_trigger(ALERT_CONNECT_OK);
    }

    /* Update connection indicator */
    if (Lvgl_lock(100)) {
        char ip_buf[20];
        wifi_sta_get_ip_str(ip_buf, sizeof(ip_buf));
        dashboard_ui_update_connection(state, wifi_sta_is_connected(), ip_buf);
        Lvgl_unlock();
    }
}

/* ---------- Sensor task ---------- */

static void sensor_task(void *arg)
{
    float temp = 0, humidity = 0;
    rtcTimeStruct_t rtc_time = {};
    int sensor_divider = 0;
    TickType_t last_sensor_tick = xTaskGetTickCount();

    for (;;) {
        /*
         * Sleep until either:
         *   - a new status image arrives (xTaskNotifyGive from audio_client), or
         *   - 1 second elapses (clock refresh cadence)
         */
        ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000));

        /* Send deferred image request if connect just happened */
        audio_client_send_pending_request();

        /* Check for status image updates immediately */
        bool img_updated = false;
        const lv_img_dsc_t *img = audio_client_get_status_image(&img_updated);
        if (img_updated && Lvgl_lock(100)) {
            dashboard_ui_update_status_image(img);
            Lvgl_unlock();
            audio_client_ack_status_image();
        }

        /* Sensor + clock updates at ~1s cadence (skip if woken early) */
        TickType_t now = xTaskGetTickCount();
        if ((now - last_sensor_tick) < pdMS_TO_TICKS(900)) {
            continue;
        }
        last_sensor_tick = now;

        /* Read RTC */
        Rtc_GetTime(&rtc_time);

        /* Read SHTC3 + battery every 5 seconds */
        if (sensor_divider == 0) {
            s_shtc3->Shtc3_Wakeup();
            vTaskDelay(pdMS_TO_TICKS(20));
            s_shtc3->Shtc3_ReadTempHumi(&temp, &humidity);
            s_shtc3->Shtc3_Sleep();

            float batt_v = Adc_GetBatteryVoltage();
            uint8_t batt = Adc_GetBatteryLevel();
            bool charging = Adc_IsCharging();

            if (Lvgl_lock(100)) {
                dashboard_ui_update_local(temp, humidity, batt, charging, batt_v);
                Lvgl_unlock();
            }
        }
        sensor_divider = (sensor_divider + 1) % 5;

        /* Update clock */
        if (Lvgl_lock(100)) {
            dashboard_ui_update_time(rtc_time.hour, rtc_time.minute, rtc_time.second,
                                     rtc_time.year, rtc_time.month, rtc_time.day,
                                     rtc_time.week);
            Lvgl_unlock();
        }
    }
}

/* ---------- Button task ---------- */

static void button_task(void *arg)
{
    for (;;) {
        /* Wait for GP18 button event (single click = bit 0, long press = bit 2) */
        EventBits_t bits = xEventGroupWaitBits(
            GP18ButtonGroups,
            set_bit_button(0) | set_bit_button(2),
            pdTRUE,              /* clear on exit */
            pdFALSE,             /* any bit */
            pdMS_TO_TICKS(500)
        );

        if (bits & set_bit_button(0)) {
            bool muted = !alert_is_muted();
            alert_mute(muted);
            ESP_LOGI(TAG, "GP18 single press: alerts %s", muted ? "muted" : "unmuted");
        }

        if (bits & set_bit_button(2)) {
            ESP_LOGI(TAG, "GP18 long press: forcing WS reconnect");
            ws_client_reconnect();
        }
    }
}
