/*
 * Developed by TComLab
 * Version: v0.1
 * Date: 2025-12-15
 */

#include "uart_mux.h"

#include <ctype.h>
#include <string.h>
#include <limits.h>

#include "dcdc_controller.h"
#include "driver/gpio.h"
#include "driver/uart.h"
#include "esp_check.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "ws2812_status.h"
#include "watch_config.h"

#ifndef CONFIG_WATCH_UART_MUX_CHANNELS
#define CONFIG_WATCH_UART_MUX_CHANNELS 5
#endif
#ifndef CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC
#define CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC 60
#endif

#if CONFIG_WATCH_UART_MUX_ENABLED

#define UART_MUX_MAX_CHANNELS CONFIG_WATCH_UART_MUX_CHANNELS

static const char *TAG = "uart_mux";

static const gpio_num_t s_select_pins[3] = {
    CONFIG_WATCH_UART_MUX_SEL_A0,
    CONFIG_WATCH_UART_MUX_SEL_A1,
    CONFIG_WATCH_UART_MUX_SEL_A2,
};

static SemaphoreHandle_t s_mutex;
static size_t s_active_channel = SIZE_MAX;
static bool s_initialized;
static int64_t s_last_heartbeat_us[UART_MUX_MAX_CHANNELS];
static TaskHandle_t s_watchdog_task;
static uint8_t s_consecutive_miss[UART_MUX_MAX_CHANNELS];
static bool s_watchdog_armed[UART_MUX_MAX_CHANNELS];
static uint32_t s_total_miss_count[UART_MUX_MAX_CHANNELS];
static uint32_t s_restart_count[UART_MUX_MAX_CHANNELS];

static uint8_t uart_mux_get_miss_limit_from_config(void);
static void uart_mux_restart_channel(size_t channel);
static void uart_mux_record_miss(size_t channel, uint8_t miss_limit);

static TickType_t uart_mux_restart_delay_ticks(void)
{
    const watch_config_t *cfg = watch_config_get();
    uint32_t sec = cfg ? cfg->dcdc_restart_off_sec : 2U;
    if (sec == 0) {
        sec = 1;
    }
    return pdMS_TO_TICKS(sec * 1000U);
}

static uint8_t uart_mux_get_miss_limit_from_config(void)
{
    const watch_config_t *cfg = watch_config_get();
    uint32_t limit = cfg ? cfg->heartbeat_miss_limit : 3U;
    if (limit == 0) {
        limit = 3U;
    }
    if (limit > UINT8_MAX) {
        limit = UINT8_MAX;
    }
    return (uint8_t)limit;
}

static void uart_mux_restart_channel(size_t channel)
{
    if (channel >= UART_MUX_MAX_CHANNELS) {
        return;
    }
    ESP_LOGW(TAG, "CH%u: перезапуск живлення після відсутності відповіді", (unsigned)channel);
    dcdc_disable(channel);
    vTaskDelay(uart_mux_restart_delay_ticks());
    dcdc_enable(channel);
    s_consecutive_miss[channel] = 0;
    s_watchdog_armed[channel] = false;
    if (s_restart_count[channel] < UINT32_MAX) {
        ++s_restart_count[channel];
    }
    s_last_heartbeat_us[channel] = esp_timer_get_time();
}

static void uart_mux_record_miss(size_t channel, uint8_t miss_limit)
{
    if (channel >= UART_MUX_MAX_CHANNELS) {
        return;
    }
    if (!dcdc_get_state(channel)) {
        return;
    }
    if (!s_watchdog_armed[channel]) {
        s_watchdog_armed[channel] = true;
        s_consecutive_miss[channel] = 0;
    }
    if (s_total_miss_count[channel] < UINT32_MAX) {
        ++s_total_miss_count[channel];
    }
    if (s_consecutive_miss[channel] < UINT8_MAX) {
        ++s_consecutive_miss[channel];
    }
    if (miss_limit == 0) {
        miss_limit = 1;
    }
    if (s_consecutive_miss[channel] >= miss_limit) {
        uart_mux_restart_channel(channel);
    }
}

static bool uart_mux_extract_numeric_field(const uint8_t *data,
                                           size_t length,
                                           const char *key,
                                           int *out_value)
{
    if (!data || !key || !out_value) {
        return false;
    }
    const size_t key_len = strlen(key);
    if (key_len == 0) {
        return false;
    }

    for (size_t i = 0; i < length; ++i) {
        if (data[i] != '"') {
            continue;
        }
        size_t j = i + 1;
        if (j + key_len >= length) {
            break;
        }
        if (memcmp(&data[j], key, key_len) != 0) {
            continue;
        }
        j += key_len;
        if (j >= length || data[j] != '"') {
            continue;
        }
        ++j;
        while (j < length && isspace((unsigned char)data[j])) {
            ++j;
        }
        if (j >= length || data[j] != ':') {
            continue;
        }
        ++j;
        while (j < length && isspace((unsigned char)data[j])) {
            ++j;
        }
        bool negative = false;
        if (j < length && (data[j] == '-' || data[j] == '+')) {
            negative = (data[j] == '-');
            ++j;
        }
        bool has_digit = false;
        int value = 0;
        while (j < length && isdigit((unsigned char)data[j])) {
            has_digit = true;
            value = value * 10 + (data[j] - '0');
            ++j;
        }
        if (has_digit) {
            *out_value = negative ? -value : value;
            return true;
        }
    }
    return false;
}

static void uart_mux_decode_status_payload(const uint8_t *data,
                                           size_t length,
                                           bool *hb_ack,
                                           bool *vpn_ok,
                                           bool *app_ok)
{
    int value = 0;
    if (hb_ack) {
        bool found = uart_mux_extract_numeric_field(data, length, "hb", &value);
        *hb_ack = found && value == 2;
    }
    if (vpn_ok) {
        bool found = uart_mux_extract_numeric_field(data, length, "VPN", &value);
        *vpn_ok = found && value != 0;
    }
    if (app_ok) {
        bool found = uart_mux_extract_numeric_field(data, length, "APP", &value);
        *app_ok = found && value != 0;
    }
}

// Перевіряє, чи містить буфер відповідь {"hb":2} від Raspberry Pi.
static bool uart_mux_contains_hb_ack(const uint8_t *data, size_t length)
{
    bool ack = false;
    uart_mux_decode_status_payload(data, length, &ack, NULL, NULL);
    return ack;
}

// Перемикає апаратний мультиплексор на вказаний канал під захистом мьютекса,
// оновлюючи таймстемп останнього heartbeat для контролю watchdog.
static esp_err_t uart_mux_select_locked(size_t channel)
{
    if (channel >= UART_MUX_MAX_CHANNELS) {
        return ESP_ERR_INVALID_ARG;
    }
    if (channel == s_active_channel) {
        return ESP_OK;
    }
    for (int bit = 0; bit < 3; ++bit) {
        int level = (channel >> bit) & 0x1;
        ESP_RETURN_ON_ERROR(gpio_set_level(s_select_pins[bit], level), TAG,
                            "GPIO set failed");
    }
    s_active_channel = channel;
    s_last_heartbeat_us[channel] = esp_timer_get_time();
    return ESP_OK;
}

// Періодично опитує всі канали, щоб зчитати heartbeat та перезапускає DCDC,
// якщо канал «мовчить» довше за CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC.
static void uart_mux_watchdog_task(void *arg)
{
    const TickType_t poll_interval = pdMS_TO_TICKS(10000);
    const TickType_t read_timeout = pdMS_TO_TICKS(300);
    const int64_t timeout_us = (int64_t)CONFIG_WATCH_UART_HEARTBEAT_TIMEOUT_SEC * 1000000LL;
    uint8_t buffer[CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN];
    const watch_config_t *cfg = watch_config_get();
    TickType_t start_delay = pdMS_TO_TICKS(cfg->heartbeat_start_delay_sec * 1000U);
    if (start_delay > 0) {
        vTaskDelay(start_delay);
    }

    while (true) {
        vTaskDelay(poll_interval);
        const watch_config_t *cfg = watch_config_get();
        if (!cfg->heartbeat_monitor_enabled) {
            continue;
        }
        uint8_t miss_limit = uart_mux_get_miss_limit_from_config();
        int64_t now = esp_timer_get_time();
        for (size_t ch = 0; ch < UART_MUX_MAX_CHANNELS; ++ch) {
            if (!dcdc_get_state(ch)) {
                s_watchdog_armed[ch] = false;
                s_consecutive_miss[ch] = 0;
                continue;
            }
            if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(20)) == pdTRUE) {
                if (uart_mux_select_locked(ch) == ESP_OK) {
                    int read = uart_read_bytes(CONFIG_WATCH_UART_PORT,
                                               buffer,
                                               sizeof(buffer),
                                               read_timeout);
                    if (read > 0) {
                        bool ack = false;
                        bool vpn_ok = false;
                        bool app_ok = false;
                        uart_mux_decode_status_payload(buffer, read, &ack, &vpn_ok, &app_ok);
                        ESP_LOGI(TAG, "UART0 RX CH%u (%d байт)%s",
                                 (unsigned)ch, read, ack ? " [HB ACK]" : "");
                        ESP_LOG_BUFFER_HEX_LEVEL(TAG, buffer, read, ESP_LOG_INFO);
                        if (ack) {
                            s_last_heartbeat_us[ch] = now;
                            s_consecutive_miss[ch] = 0;
                            s_watchdog_armed[ch] = true;
                            ws2812_status_set_service_state(ch, vpn_ok, app_ok);
                        } else {
                            uart_mux_record_miss(ch, miss_limit);
                        }
                    } else {
                        uart_mux_record_miss(ch, miss_limit);
                        ESP_LOGD(TAG, "UART0 RX CH%u: немає відповіді у watchdog (%u)",
                                 (unsigned)ch, (unsigned)s_consecutive_miss[ch]);
                    }
                }
                xSemaphoreGive(s_mutex);
            }

            if (dcdc_get_state(ch) && s_watchdog_armed[ch] && s_consecutive_miss[ch] >= miss_limit) {
                uart_mux_restart_channel(ch);
            }

            if (dcdc_get_state(ch) && s_last_heartbeat_us[ch] > 0 &&
                (now - s_last_heartbeat_us[ch]) > timeout_us) {
                uart_mux_restart_channel(ch);
            }
        }
    }
}

#endif // CONFIG_WATCH_UART_MUX_ENABLED

// Налаштовує GPIO-вибірники, драйвер UART та створює watchdog-задачу для
// мультиплексора; повторний виклик просто повертає ESP_OK.
esp_err_t uart_mux_init(void)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED
    return ESP_ERR_NOT_SUPPORTED;
#else
    if (s_initialized) {
        return ESP_OK;
    }

    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_DISABLE,
        .mode = GPIO_MODE_OUTPUT,
        .pull_down_en = GPIO_PULLDOWN_DISABLE,
        .pull_up_en = GPIO_PULLUP_DISABLE,
        .pin_bit_mask = 0,
    };
    for (int i = 0; i < 3; ++i) {
        io_conf.pin_bit_mask = 1ULL << s_select_pins[i];
        ESP_RETURN_ON_ERROR(gpio_config(&io_conf), TAG, "GPIO config failed");
        ESP_RETURN_ON_ERROR(gpio_set_level(s_select_pins[i], 0), TAG, "GPIO init level failed");
    }
    s_active_channel = 0;

    uart_config_t uart_cfg = {
        .baud_rate = CONFIG_WATCH_UART_BAUD,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_APB,
    };
    ESP_RETURN_ON_ERROR(uart_param_config(CONFIG_WATCH_UART_PORT, &uart_cfg), TAG, "UART config failed");
    ESP_RETURN_ON_ERROR(uart_set_pin(CONFIG_WATCH_UART_PORT,
                                     CONFIG_WATCH_UART_TX_GPIO,
                                     CONFIG_WATCH_UART_RX_GPIO,
                                     UART_PIN_NO_CHANGE,
                                     UART_PIN_NO_CHANGE),
                        TAG, "UART pin assign failed");
    ESP_RETURN_ON_ERROR(uart_driver_install(CONFIG_WATCH_UART_PORT,
                                            CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN * 2,
                                            CONFIG_WATCH_UART_MUX_DEFAULT_READ_LEN * 2,
                                            0, NULL, 0),
                        TAG, "UART driver install failed");

    s_mutex = xSemaphoreCreateMutex();
    ESP_RETURN_ON_FALSE(s_mutex, ESP_ERR_NO_MEM, TAG, "mutex alloc failed");

    int64_t now = esp_timer_get_time();
    for (size_t ch = 0; ch < UART_MUX_MAX_CHANNELS; ++ch) {
        s_last_heartbeat_us[ch] = now;
        s_consecutive_miss[ch] = 0;
        s_watchdog_armed[ch] = false;
        s_total_miss_count[ch] = 0;
        s_restart_count[ch] = 0;
    }

    if (xTaskCreate(uart_mux_watchdog_task, "uart_mux_wd", 4096, NULL, 5, &s_watchdog_task) != pdPASS) {
        return ESP_ERR_NO_MEM;
    }

    s_initialized = true;
    ESP_LOGI(TAG, "UART мультиплексор активовано, каналів: %d", UART_MUX_MAX_CHANNELS);
    return ESP_OK;
#endif
}

// Повертає ознаку ініціалізації модулю UART мультиплексора.
bool uart_mux_ready(void)
{
#if CONFIG_WATCH_UART_MUX_ENABLED
    return s_initialized;
#else
    return false;
#endif
}
// Кількість доступних каналів, визначених у конфігурації.
size_t uart_mux_channel_count(void)
{
    return CONFIG_WATCH_UART_MUX_CHANNELS;
}

// Перемикається на канал, передає буфер даних через загальний UART та
// захищає доступ до шини мьютексом, щоб уникнути гонок між задачами.
esp_err_t uart_mux_write(size_t channel, const uint8_t *data, size_t length, TickType_t timeout)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED
    return ESP_ERR_NOT_SUPPORTED;
#else
    if (!s_initialized) {
        return ESP_ERR_INVALID_STATE;
    }
    if (channel >= UART_MUX_MAX_CHANNELS) {
        return ESP_ERR_INVALID_ARG;
    }
    if (!data || length == 0) {
        return ESP_OK;
    }
    if (xSemaphoreTake(s_mutex, timeout) != pdTRUE) {
        return ESP_ERR_TIMEOUT;
    }
    esp_err_t err = uart_mux_select_locked(channel);
    if (err == ESP_OK) {
        int written = uart_write_bytes(CONFIG_WATCH_UART_PORT, (const char *)data, length);
        if (written < 0 || (size_t)written != length) {
            err = ESP_FAIL;
        } else {
            if (uart_wait_tx_done(CONFIG_WATCH_UART_PORT, timeout) != ESP_OK) {
                err = ESP_ERR_TIMEOUT;
            }
        }
    }
    xSemaphoreGive(s_mutex);
    return err;
#endif
}

// Читає дані з вказаного каналу, оновлюючи heartbeat під час успішного
// зчитування, і повертає кількість байтів через out_length.
esp_err_t uart_mux_read(size_t channel, uint8_t *buffer, size_t buffer_size, size_t *out_length, TickType_t timeout)
{
#if !CONFIG_WATCH_UART_MUX_ENABLED
    return ESP_ERR_NOT_SUPPORTED;
#else
    if (!s_initialized) {
        return ESP_ERR_INVALID_STATE;
    }
    if (channel >= UART_MUX_MAX_CHANNELS || !buffer || buffer_size == 0) {
        return ESP_ERR_INVALID_ARG;
    }
    if (xSemaphoreTake(s_mutex, timeout) != pdTRUE) {
        return ESP_ERR_TIMEOUT;
    }
    esp_err_t err = uart_mux_select_locked(channel);
    if (err == ESP_OK) {
        int read = uart_read_bytes(CONFIG_WATCH_UART_PORT, buffer, buffer_size, timeout);
        if (read < 0) {
            err = ESP_FAIL;
        } else {
            if (out_length) {
                *out_length = (size_t)read;
            }
            if (read > 0) {
                s_last_heartbeat_us[channel] = esp_timer_get_time();
            }
        }
    }
    xSemaphoreGive(s_mutex);
    return err;
#endif
}

void uart_mux_process_rx(size_t channel, const uint8_t *data, size_t length)
{
#if CONFIG_WATCH_UART_MUX_ENABLED
    if (!s_initialized || channel >= UART_MUX_MAX_CHANNELS || !data || length == 0) {
        return;
    }
    bool ack = false;
    bool vpn_ok = false;
    bool app_ok = false;
    uart_mux_decode_status_payload(data, length, &ack, &vpn_ok, &app_ok);
    int64_t now = esp_timer_get_time();
    if (xSemaphoreTake(s_mutex, pdMS_TO_TICKS(10)) == pdTRUE) {
        s_last_heartbeat_us[channel] = now;
        if (ack) {
            s_consecutive_miss[channel] = 0;
            s_watchdog_armed[channel] = true;
        }
        xSemaphoreGive(s_mutex);
    }
    if (ack) {
        ws2812_status_set_service_state(channel, vpn_ok, app_ok);
    } else {
        uart_mux_record_miss(channel, uart_mux_get_miss_limit_from_config());
    }
#else
    (void)channel;
    (void)data;
    (void)length;
#endif
}

void uart_mux_report_miss(size_t channel)
{
#if CONFIG_WATCH_UART_MUX_ENABLED
    if (!s_initialized) {
        return;
    }
    uart_mux_record_miss(channel, uart_mux_get_miss_limit_from_config());
#else
    (void)channel;
#endif
}

void uart_mux_get_channel_stats(size_t channel, uart_mux_channel_stats_t *out_stats)
{
    if (!out_stats) {
        return;
    }
#if CONFIG_WATCH_UART_MUX_ENABLED
    if (!s_initialized || channel >= UART_MUX_MAX_CHANNELS) {
        out_stats->missed_heartbeats = 0;
        out_stats->restart_count = 0;
        return;
    }
    out_stats->missed_heartbeats = s_total_miss_count[channel];
    out_stats->restart_count = s_restart_count[channel];
#else
    (void)channel;
    out_stats->missed_heartbeats = 0;
    out_stats->restart_count = 0;
#endif
}
