#include "usb_cdc_cli.h"

#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#include "dcdc_controller.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "tinyusb.h"
#include "tusb_cdc_acm.h"
#include "ws2812_status.h"
#include "watch_config.h"
#include "esp_system.h"
#include "ina226_monitor.h"
#include "uart_mux.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/soc.h"

#define CLI_LINE_MAX_LEN 128
#define CLI_QUEUE_LEN 8

#ifndef CONFIG_TINYUSB_CDC_RX_BUFSIZE
#define CONFIG_TINYUSB_CDC_RX_BUFSIZE 64
#endif
#ifndef CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN
#define CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN 128
#endif

typedef struct {
    size_t length;
    uint8_t data[CONFIG_TINYUSB_CDC_RX_BUFSIZE];
} usb_cli_msg_t;

static const char *TAG = "usb_cli";

static QueueHandle_t s_cli_queue;
static bool s_cli_initialized;
static bool s_host_ready;

static void usb_cli_task(void *arg);
static void usb_cli_process_line(char *line);
static void usb_cli_prompt(void);
static void usb_cli_enter_bootloader(void);
static void usb_cli_handle_config(char *args);
static void usb_cli_print_config(void);
static void usb_cli_handle_reset(void);

static void usb_cli_write_raw(const char *data, size_t len)
{
    if (!s_host_ready || len == 0) {
        return;
    }

    if (tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, (uint8_t *)data, len) == ESP_OK) {
        esp_err_t err = tinyusb_cdcacm_write_flush(TINYUSB_CDC_ACM_0, 0);
        if (err != ESP_OK) {
            ESP_LOGW(TAG, "CDC flush error: %s", esp_err_to_name(err));
        }
    }
}

static void usb_cli_printf(const char *fmt, ...)
{
    if (!s_host_ready) {
        return;
    }

    char buffer[192];
    va_list args;
    va_start(args, fmt);
    int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
    va_end(args);
    if (len > 0) {
        if (len >= (int)sizeof(buffer)) {
            len = (int)sizeof(buffer) - 1;
        }
        usb_cli_write_raw(buffer, len);
    }
}

static const char *PROMPT = "\r\nwatch> ";

static char *usb_cli_trim(char *str)
{
    while (*str && isspace((unsigned char)*str)) {
        str++;
    }
    char *end = str + strlen(str);
    while (end > str && isspace((unsigned char)*(end - 1))) {
        *(--end) = '\0';
    }
    return str;
}

static bool usb_cli_parse_channel(const char *token, dcdc_channel_t *channel)
{
    if (!token) {
        return false;
    }
    char *end = NULL;
    long val = strtol(token, &end, 10);
    if (end == token || val < 0 || val >= DCDC_CHANNEL_COUNT) {
        return false;
    }
    *channel = (dcdc_channel_t)val;
    return true;
}

static void usb_cli_print_status(void)
{
    usb_cli_printf("\r\nСтан каналів:\r\n");
    for (size_t i = 0; i < dcdc_channel_count(); ++i) {
        dcdc_channel_t ch = (dcdc_channel_t)i;
        uart_mux_channel_stats_t stats = {0};
        if (uart_mux_ready()) {
            uart_mux_get_channel_stats(i, &stats);
        }
        usb_cli_printf(" - CH%u: %s (GPIO %d) | miss=%u restart=%u\r\n",
                       (unsigned)i,
                       dcdc_get_state(ch) ? "ON" : "OFF",
                       (int)dcdc_get_gpio(ch),
                       (unsigned)stats.missed_heartbeats,
                       (unsigned)stats.restart_count);
    }
}

static void usb_cli_prompt(void)
{
    usb_cli_write_raw(PROMPT, strlen(PROMPT));
}

static void usb_cli_enter_bootloader(void)
{
    usb_cli_printf("\r\nПерехід у режим прошивки esptool...\r\n"
                   "Після перезавантаження з’явиться ROM-порт USB CDC/Serial.\r\n"
                   "Запустіть esptool.py або idf.py flash та прошийте пристрій. "
                   "Для виходу з bootloader виконайте 'esptool.py --after hard_reset reset' або перезавантажте живлення.\r\n");
    vTaskDelay(pdMS_TO_TICKS(100));
    tinyusb_driver_uninstall();
    REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT);
    esp_restart();
}

static void usb_cli_handle_switch(const char *cmd, char *args)
{
    dcdc_channel_t channel;
    char *ctx = NULL;
    char *channel_str = strtok_r(args, " ", &ctx);
    if (!usb_cli_parse_channel(channel_str, &channel)) {
        usb_cli_printf("\r\nНевірний номер каналу\r\n");
        return;
    }

    esp_err_t err = ESP_OK;
    if (strcasecmp(cmd, "enable") == 0) {
        err = dcdc_enable(channel);
    } else if (strcasecmp(cmd, "disable") == 0) {
        err = dcdc_disable(channel);
    } else {
        err = dcdc_toggle(channel);
    }

    if (err != ESP_OK) {
        usb_cli_printf("\r\nПомилка керування каналом: %s\r\n", esp_err_to_name(err));
    } else {
        bool state = dcdc_get_state(channel);
        usb_cli_printf("\r\nКанал %d -> %s\r\n", channel, state ? "ON" : "OFF");
        ws2812_status_set_channel_state(channel, state);
    }
}

static void usb_cli_print_measurement(size_t channel, const ina226_reading_t *reading)
{
    usb_cli_printf("\r\nCH%u: %.2f В, %.3f А, %.3f Вт",
                   (unsigned)channel, reading->voltage_v, reading->current_a, reading->power_w);
}

static void usb_cli_handle_sense(char *args)
{
    if (!ina226_monitor_ready()) {
        usb_cli_printf("\r\nМоніторинг INA226 недоступний\r\n");
        return;
    }

    ina226_reading_t reading = {0};
    if (ina226_monitor_sample(&reading) == ESP_OK) {
        usb_cli_print_measurement(0, &reading);
    } else {
        usb_cli_printf("\r\nНе вдалося зчитати дані з INA226\r\n");
    }
}

static void usb_cli_handle_uart(char *args)
{
    if (!uart_mux_ready()) {
        usb_cli_printf("\r\nUART мультиплексор недоступний\r\n");
        return;
    }

    char *ctx = NULL;
    char *action = strtok_r(args, " ", &ctx);
    if (!action) {
        usb_cli_printf("\r\nUART usage: uart send <channel> <text> | uart read <channel> [len]\r\n");
        return;
    }

    char *channel_str = strtok_r(NULL, " ", &ctx);
    dcdc_channel_t channel;
    if (!usb_cli_parse_channel(channel_str, &channel)) {
        usb_cli_printf("\r\nНевірний номер каналу\r\n");
        return;
    }

    if (strcasecmp(action, "send") == 0) {
        char *payload = ctx;
        if (!payload || *payload == '\0') {
            usb_cli_printf("\r\nНемає даних для відправлення\r\n");
            return;
        }
        size_t len = strlen(payload);
        if (uart_mux_write(channel, (const uint8_t *)payload, len, pdMS_TO_TICKS(200)) == ESP_OK) {
            usb_cli_printf("\r\nВідправлено %u байт\r\n", (unsigned)len);
        } else {
            usb_cli_printf("\r\nПомилка відправлення UART\r\n");
        }
    } else if (strcasecmp(action, "read") == 0) {
        char *len_str = ctx ? strtok_r(NULL, " ", &ctx) : NULL;
        size_t req_len = len_str ? strtoul(len_str, NULL, 10) : CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN;
        if (req_len == 0) {
            req_len = CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN;
        }
        if (req_len > 256) {
            req_len = 256;
        }
        uint8_t buffer[256];
        size_t received = 0;
        if (uart_mux_read(channel, buffer, req_len, &received, pdMS_TO_TICKS(200)) == ESP_OK && received > 0) {
            usb_cli_printf("\r\nCH%u UART (%u байт): ", (unsigned)channel, (unsigned)received);
            for (size_t i = 0; i < received; ++i) {
                usb_cli_write_raw((char *)&buffer[i], 1);
            }
        } else {
            usb_cli_printf("\r\nДані відсутні\r\n");
        }
    } else {
        usb_cli_printf("\r\nНевірна дія '%s'\r\n", action);
    }
}

static void usb_cli_process_line(char *line)
{
    char *trimmed = usb_cli_trim(line);
    if (*trimmed == '\0') {
        return;
    }

    char *save_ptr = NULL;
    char *cmd = strtok_r(trimmed, " ", &save_ptr);
    if (!cmd) {
        return;
    }

    if (strcasecmp(cmd, "help") == 0) {
        usb_cli_printf(
            "\r\nДоступні команди:\r\n"
            "  help               - показати цю довідку\r\n"
            "  status             - показати стан усіх каналів DCDC\r\n"
            "  enable <n>         - увімкнути канал n (0..4)\r\n"
            "  disable <n>        - вимкнути канал n\r\n"
            "  toggle <n>         - перемкнути канал n\r\n"
            "  sense              - виміряти напругу/струм/потужність INA226\r\n"
            "  uart send <n> <msg> - надіслати текстове повідомлення в Raspberry Pi n\r\n"
            "  uart read <n> [len] - прочитати до [len] байт відповіді від Raspberry Pi n\r\n"
            "  config show        - показати збережені таймінги heartbeat/DCDC\r\n"
            "  config set hb_period <сек>      - змінити інтервал відправки heartbeat\r\n"
            "  config set dcdc_off <сек>       - задати тривалість вимкнення DCDC при рестарті\r\n"
            "  config set hb_start <сек>       - налаштувати затримку перед стартом опитування після boot\r\n"
            "  config set hb_monitor <0|1>     - увімкнути/вимкнути контроль відповіді heartbeat\r\n"
            "  config set hb_miss <шт>         - кількість пропусків відповіді до рестарту каналу\r\n"
            "  reset              - м'яко перезавантажити ESP32-S3\r\n"
            "  bootloader         - перезавантажити ESP32-S3 у ROM bootloader для esptool\r\n");
    } else if (strcasecmp(cmd, "status") == 0) {
        usb_cli_print_status();
    } else if (strcasecmp(cmd, "enable") == 0 ||
               strcasecmp(cmd, "disable") == 0 ||
               strcasecmp(cmd, "toggle") == 0) {
        usb_cli_handle_switch(cmd, save_ptr);
    } else if (strcasecmp(cmd, "sense") == 0) {
        usb_cli_handle_sense(save_ptr);
    } else if (strcasecmp(cmd, "uart") == 0) {
        usb_cli_handle_uart(save_ptr);
    } else if (strcasecmp(cmd, "config") == 0) {
        usb_cli_handle_config(save_ptr);
    } else if (strcasecmp(cmd, "reset") == 0) {
        usb_cli_handle_reset();
    } else if (strcasecmp(cmd, "bootloader") == 0) {
        usb_cli_enter_bootloader();
    } else {
        usb_cli_printf("\r\nНевідома команда '%s'\r\n", cmd);
    }
}

static void usb_cli_print_config(void)
{
    const watch_config_t *cfg = watch_config_get();
    usb_cli_printf(
        "\r\nПоточні налаштування:\r\n"
        "  hb_period: %u с\r\n"
        "  dcdc_off: %u с\r\n"
        "  hb_start_delay: %u с\r\n"
        "  hb_monitor: %s\r\n"
        "  hb_miss_limit: %u зап.\r\n",
        (unsigned)cfg->heartbeat_period_sec,
        (unsigned)cfg->dcdc_restart_off_sec,
        (unsigned)cfg->heartbeat_start_delay_sec,
        cfg->heartbeat_monitor_enabled ? "on" : "off",
        (unsigned)cfg->heartbeat_miss_limit);
}

static void usb_cli_handle_config(char *args)
{
    if (!args || *args == '\0') {
        usb_cli_print_config();
        return;
    }

    char *ctx = NULL;
    char *action = strtok_r(args, " ", &ctx);
    if (!action || strcasecmp(action, "show") == 0) {
        usb_cli_print_config();
        return;
    }

    if (strcasecmp(action, "set") != 0) {
        usb_cli_printf("\r\nВикористання: config show | config set <hb_period|dcdc_off|hb_start|hb_monitor|hb_miss> <знач>\r\n");
        return;
    }

    char *param = strtok_r(NULL, " ", &ctx);
    char *value_str = strtok_r(NULL, " ", &ctx);
    if (!param || !value_str) {
        usb_cli_printf("\r\nВкажіть параметр і значення в секундах\r\n");
        return;
    }

    uint32_t value = (uint32_t)strtoul(value_str, NULL, 10);

    watch_config_t new_cfg = *watch_config_get();
    if (strcasecmp(param, "hb_period") == 0) {
        if (value == 0) {
            usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
            return;
        }
        new_cfg.heartbeat_period_sec = value;
    } else if (strcasecmp(param, "dcdc_off") == 0) {
        if (value == 0) {
            usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
            return;
        }
        new_cfg.dcdc_restart_off_sec = value;
    } else if (strcasecmp(param, "hb_start") == 0 ||
               strcasecmp(param, "hb_start_delay") == 0) {
        if (value == 0) {
            usb_cli_printf("\r\nЗначення має бути більше нуля\r\n");
            return;
        }
        new_cfg.heartbeat_start_delay_sec = value;
    } else if (strcasecmp(param, "hb_monitor") == 0) {
        if (value != 0 && value != 1) {
            usb_cli_printf("\r\nhb_monitor приймає 0 або 1\r\n");
            return;
        }
        new_cfg.heartbeat_monitor_enabled = (value != 0);
    } else if (strcasecmp(param, "hb_miss") == 0 ||
               strcasecmp(param, "hb_miss_limit") == 0) {
        if (value == 0) {
            usb_cli_printf("\r\nhb_miss має бути більше нуля\r\n");
            return;
        }
        new_cfg.heartbeat_miss_limit = value;
    } else {
        usb_cli_printf("\r\nНевідомий параметр '%s'\r\n", param);
        return;
    }

    esp_err_t err = watch_config_save(&new_cfg);
    if (err == ESP_OK) {
        usb_cli_printf("\r\nПараметр оновлено\r\n");
        usb_cli_print_config();
    } else {
        usb_cli_printf("\r\nПомилка збереження конфігурації: %s\r\n", esp_err_to_name(err));
    }
}

static void usb_cli_task(void *arg)
{
    usb_cli_msg_t msg;
    char line[CLI_LINE_MAX_LEN];
    size_t line_len = 0;

    while (xQueueReceive(s_cli_queue, &msg, portMAX_DELAY)) {
        for (size_t i = 0; i < msg.length; ++i) {
            char ch = (char)msg.data[i];

            if (ch == '\r' || ch == '\n') {
                if (line_len > 0) {
                    line[line_len] = '\0';
                    usb_cli_process_line(line);
                    line_len = 0;
                }
                usb_cli_write_raw("\r\n", 2);
                usb_cli_prompt();
            } else if (ch == 0x7F || ch == '\b') {
                if (line_len > 0) {
                    line_len--;
                    usb_cli_write_raw("\b \b", 3);
                }
            } else if (isprint((unsigned char)ch)) {
                if (line_len < CLI_LINE_MAX_LEN - 1) {
                    line[line_len++] = ch;
                    usb_cli_write_raw(&ch, 1);
                } else {
                    usb_cli_printf("\r\nРядок занадто довгий\r\n");
                    line_len = 0;
                    usb_cli_prompt();
                }
            }
        }
    }
}

static void usb_cdc_rx_callback(int itf, cdcacm_event_t *event)
{
    if (!s_cli_queue) {
        return;
    }

    usb_cli_msg_t msg = { 0 };
    size_t rx_size = 0;
    esp_err_t ret = tinyusb_cdcacm_read(itf, msg.data, sizeof(msg.data), &rx_size);
    if (ret == ESP_OK && rx_size > 0) {
        msg.length = rx_size;
        if (xQueueSend(s_cli_queue, &msg, 0) != pdTRUE) {
            ESP_LOGW(TAG, "CLI queue overflow, втрачено %u байт", (unsigned)rx_size);
        }
    }
}

static void usb_cdc_line_state_changed_callback(int itf, cdcacm_event_t *event)
{
    const bool dtr = event->line_state_changed_data.dtr;
    const bool rts = event->line_state_changed_data.rts;
    ESP_LOGI(TAG, "CDC лінія %d змінилась: DTR=%d RTS=%d", itf, dtr, rts);
    s_host_ready = dtr;
    if (s_host_ready) {
        usb_cli_printf("\r\nwatch-watch CLI готовий. Надрукуйте 'help'.\r\n");
        usb_cli_prompt();
    }
}

esp_err_t usb_cdc_cli_init(void)
{
    if (s_cli_initialized) {
        return ESP_OK;
    }

    s_cli_queue = xQueueCreate(CLI_QUEUE_LEN, sizeof(usb_cli_msg_t));
    if (!s_cli_queue) {
        return ESP_ERR_NO_MEM;
    }

    if (xTaskCreate(usb_cli_task, "usb_cli", 4096, NULL, 5, NULL) != pdPASS) {
        vQueueDelete(s_cli_queue);
        s_cli_queue = NULL;
        return ESP_ERR_NO_MEM;
    }

    const tinyusb_config_t tusb_cfg = {
        .device_descriptor = NULL,
        .string_descriptor = NULL,
        .external_phy = false,
#if (TUD_OPT_HIGH_SPEED)
        .fs_configuration_descriptor = NULL,
        .hs_configuration_descriptor = NULL,
        .qualifier_descriptor = NULL,
#else
        .configuration_descriptor = NULL,
#endif
    };

    ESP_RETURN_ON_ERROR(tinyusb_driver_install(&tusb_cfg), TAG, "TinyUSB init failed");

    const tinyusb_config_cdcacm_t acm_cfg = {
        .usb_dev = TINYUSB_USBDEV_0,
        .cdc_port = TINYUSB_CDC_ACM_0,
        .rx_unread_buf_sz = CONFIG_TINYUSB_CDC_RX_BUFSIZE,
        .callback_rx = usb_cdc_rx_callback,
        .callback_rx_wanted_char = NULL,
        .callback_line_state_changed = NULL,
        .callback_line_coding_changed = NULL,
    };

    ESP_RETURN_ON_ERROR(tusb_cdc_acm_init(&acm_cfg), TAG, "CDC init failed");
    ESP_RETURN_ON_ERROR(
        tinyusb_cdcacm_register_callback(TINYUSB_CDC_ACM_0,
                                         CDC_EVENT_LINE_STATE_CHANGED,
                                         usb_cdc_line_state_changed_callback),
        TAG,
        "Line state callback error");

    s_cli_initialized = true;
    ESP_LOGI(TAG, "USB CDC CLI ініціалізовано");
    return ESP_OK;
}
static void usb_cli_handle_reset(void)
{
    usb_cli_printf("\r\nПерезавантаження пристрою...\r\n");
    vTaskDelay(pdMS_TO_TICKS(100));
    esp_restart();
}
