#include "serial.h"

#include "board.h"
#include "charger_config.h"
#include "connector.h"
#include "cp.h"
#include "debug.h"
#include "isr_opt.h"
#include "fire_alarm.h"
#include "psu_control.h"
#include "serial_control.h"
#include <stdint.h>
#include <string.h>

extern UART_HandleTypeDef huart3;
extern uint8_t config_initialized;

static void send_state(void);
static void CCS_SendResetReason(void);

CCS_MaxLoad_t CCS_MaxLoad;

uint32_t CCS_Power = 0;
uint32_t CCS_EnergyWs = 0;
uint32_t CCS_Energy = 0;

uint32_t last_cmd_sent = 0;
uint32_t last_stop_sent = 0;
CONN_Control_t last_cmd = CMD_NONE;
uint8_t ev_enable_output = 0;

#define CMD_INTERVAL 10
#define EVEREST_TIMEOUT_WARN_MS 5000u
#define EVEREST_TIMEOUT_STOP_MS 10000u
#define UART3_REINIT_TIMEOUT_MS 1500u

static uint8_t rx_buffer[MAX_RX_BUFFER_SIZE];
static uint8_t tx_buffer[MAX_TX_BUFFER_SIZE];
static uint8_t tx_pending_buffer[MAX_TX_BUFFER_SIZE];
static uint16_t tx_pending_len = 0;
static uint8_t uart3_tx_busy = 0;

uint8_t ESTOP = 0;
uint8_t REPLUG = 0;

static uint8_t enabled = 0;
static uint8_t pwm_duty_percent = 100;
uint8_t isolation_enable = 0;
static uint32_t last_host_seen = 0;
static uint8_t fake_500_voltage_mode = 0;
static uint8_t everest_timed_out = 0;
static uint8_t everest_timeout_warn_latched = 0;
static uint8_t everest_timeout_stop_latched = 0;
static uint32_t uart3_last_packet_tick = 0;
static uint32_t uart3_last_reinit_tick = 0;

CCS_State_t CCS_State;
CCS_EvInfo_t CCS_EvInfo;
CONN_State_t CCS_EvseState;
CCS_ConnectorState_t CCS_ConnectorState = CCS_UNKNOWN;

ISR_FAST static uint8_t process_received_packet(const uint8_t* packet, uint16_t packet_len);
static void CCS_UART3_Watchdog(void);
static void CCS_LogUart3Error(const char *tag);

ISR_FAST static void uart3_log_hal_error(uint8_t uart_num, uint32_t err) {
    if (err == HAL_UART_ERROR_NONE) {
        log_printf(LOG_ERR, "UART%u HAL error decode: NONE\n", uart_num);
        return;
    }
    log_printf(LOG_ERR, "UART%u HAL error decode: %s%s%s%s%s%s raw=0x%08lx\n",
               uart_num,
               (err & HAL_UART_ERROR_PE) ? "PE " : "",
               (err & HAL_UART_ERROR_NE) ? "NE " : "",
               (err & HAL_UART_ERROR_FE) ? "FE " : "",
               (err & HAL_UART_ERROR_ORE) ? "ORE " : "",
               (err & HAL_UART_ERROR_DMA) ? "DMA " : "",
#ifdef HAL_UART_ERROR_INVALID_CALLBACK
               (err & HAL_UART_ERROR_INVALID_CALLBACK) ? "INV_CB " : "",
#else
               "",
#endif
               (unsigned long)err);
}

ISR_FAST static void uart3_arm_rx_or_log(const char *where) {
    HAL_StatusTypeDef st = HAL_UARTEx_ReceiveToIdle_DMA(&huart3, rx_buffer, sizeof(rx_buffer));
    if (st == HAL_OK) {
        return;
    }
    uint32_t err_after = HAL_UART_GetError(&huart3);
    log_printf(LOG_ERR,
               "UART3 RX arm failed (%s): HAL_Status=%d err_after=0x%08lx\n",
               where, (int)st, (unsigned long)err_after);
    uart3_log_hal_error(3u, err_after);
    CCS_LogUart3Error("UART3 RX arm failed details");
    if (err_after != HAL_UART_ERROR_NONE) {
        (void)HAL_UART_AbortReceive(&huart3);
    }
}

ISR_FAST void CCS_RxEventCallback(UART_HandleTypeDef *huart, uint16_t size) {
    if (huart != &huart3) {
        log_printf(LOG_WARN, "UART3 RX drop: wrong huart in RxEventCallback (size=%u)\n",
                   (unsigned)size);
        return;
    }
    if (size == 0u) {
        log_printf(LOG_WARN, "UART3 RX drop: RxEvent size=0 (idle, no payload)\n");
        uart3_arm_rx_or_log("RxEventCallback");
        return;
    }
    if (size > sizeof(rx_buffer)) {
        log_printf(LOG_ERR, "UART3 RX drop: size=%u > rx_buffer %u (overflow, not parsed)\n",
                   (unsigned)size, (unsigned)sizeof(rx_buffer));
        uart3_arm_rx_or_log("RxEventCallback");
        return;
    }
    uart3_last_packet_tick = HAL_GetTick();
    uart3_last_reinit_tick = uart3_last_packet_tick;
    process_received_packet(rx_buffer, size);
    uart3_arm_rx_or_log("RxEventCallback");
}

ISR_FAST void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
    uint32_t error = HAL_UART_GetError(huart);
    uint8_t uart_num =
        (huart == &huart2) ? 2 :
        (huart == &huart3) ? 3 :
        (huart == &huart5) ? 5 : 0;
    log_printf(LOG_ERR,
               "UART%u HAL error (ISR): raw=0x%08lx — RX may be corrupted until re-arm\n",
               uart_num, (unsigned long)error);
    uart3_log_hal_error(uart_num, error);
    (void)HAL_UART_AbortReceive(huart);
    (void)HAL_UART_AbortTransmit(huart);
    if (huart == &huart3) {
        uart3_tx_busy = 0;
        uart3_arm_rx_or_log("ErrorCallback");
    } else {
        SC_RecoverUartDma(huart);
    }
}

void CCS_TxCpltCallback(UART_HandleTypeDef *huart) {
    if (huart != &huart3) {
        return;
    }
    uart3_tx_busy = 0;
    if (tx_pending_len > 0) {
        memcpy(tx_buffer, tx_pending_buffer, tx_pending_len);
        uart3_tx_busy = 1;
        if (HAL_UART_Transmit_DMA(&huart3, tx_buffer, tx_pending_len) != HAL_OK) {
            uart3_tx_busy = 0;
            CCS_LogUart3Error("UART3 TX DMA resend failed");
        }
        tx_pending_len = 0;
    }
}

void CCS_SerialLoop(void) {
    static uint32_t tick;
    if ((int32_t)(HAL_GetTick() - tick) < 1) return;
    tick = HAL_GetTick();
    
    static uint32_t replug_tick = 0;
    static uint32_t replug_watchdog_tick = 0;
    static uint32_t replug_watchdog1_tick = 0;
    static uint32_t last_state_sent = 0;
    static uint32_t force_unlock_tick = 0;
    static uint32_t stop_tick = 0;

    CCS_UART3_Watchdog();
    FireAlarm_Maintain();

    if (CONN.connControl != CMD_NONE) {
        last_cmd = CONN.connControl;
    }

    if (CONN.connControl == CMD_FORCE_UNLOCK) {
        if (force_unlock_tick == 0) {
            force_unlock_tick = HAL_GetTick();
        } else if ((int32_t)(HAL_GetTick() - force_unlock_tick) >= 10000) {
            CONN.connControl = CMD_NONE;
            force_unlock_tick = 0;
        }
    } else {
        force_unlock_tick = 0;
    }

    if (CONN.connControl == CMD_STOP) {
        if (stop_tick == 0) {
            stop_tick = HAL_GetTick();
        } else if ((int32_t)(HAL_GetTick() - stop_tick) >= 1000) {
            CONN.connControl = CMD_NONE;
            stop_tick = 0;
        }
    } else {
        stop_tick = 0;
    }

    if((int32_t)(HAL_GetTick() - last_cmd_sent) > (int32_t)CMD_INTERVAL){
        if ((int32_t)(HAL_GetTick() - last_state_sent) >= 200) {
            send_state();
            last_state_sent = HAL_GetTick();
        }

        if (ESTOP) {
            log_printf(LOG_ERR, "ESTOP triggered\n");
            CCS_SendEmergencyStop();
            ESTOP = 0;
        }

        if (((CONN.connControl == CMD_STOP) ||
             (CONN.connControl == CMD_FORCE_UNLOCK) ||
             (CONN.chargingError != CONN_NO_ERROR) ||
             FireAlarm_IsLatched()) &&
            ((int32_t)(HAL_GetTick() - last_stop_sent) > 1000)) {
            last_stop_sent = HAL_GetTick();
            log_printf(LOG_WARN, "Stopping charging...\n");
            if (CONN.connControl == CMD_FORCE_UNLOCK) {
                CP_SetDuty(100);
            }
            CCS_SendEmergencyStop();
        }

        if (((CCS_EvseState == FinishedEV) || (CCS_EvseState == FinishedEVSE)) &&
            ((int32_t)(HAL_GetTick() - last_stop_sent) > 1000)) {
            last_stop_sent = HAL_GetTick();
            log_printf(LOG_WARN, "FinishedEV, stopping...\n");
            CCS_SendEmergencyStop();
        }
    }

    (void)replug_watchdog_tick;
    (void)replug_watchdog1_tick;

    uint8_t host_timeout_warn = (last_host_seen > 0u) && ((int32_t)(HAL_GetTick() - last_host_seen) > (int32_t)EVEREST_TIMEOUT_WARN_MS);
    uint8_t host_timeout_stop = (last_host_seen > 0u) && ((int32_t)(HAL_GetTick() - last_host_seen) > (int32_t)EVEREST_TIMEOUT_STOP_MS);
    uint8_t host_timed_out = host_timeout_stop;

    if (host_timeout_warn && !everest_timeout_warn_latched) {
        log_printf(LOG_ERR, "Everest timeout\n");
        everest_timeout_warn_latched = 1;
    }

    if (host_timeout_stop && !everest_timeout_stop_latched) {
        log_printf(LOG_ERR, "Everest timeout, stopping charging...\n");
        everest_timeout_stop_latched = 1;
    }

    if (!host_timeout_warn) {
        everest_timeout_warn_latched = 0;
        everest_timeout_stop_latched = 0;
    }

    everest_timed_out = host_timeout_stop;
    switch(CCS_ConnectorState){
            case CCS_UNKNOWN:
                if (FireAlarm_IsLatched()) {
                    CCS_ConnectorState = CCS_DISABLED;
                    break;
                }
                RELAY_Write(RELAY_CP, 0);
                CONN_SetState(Unknown);
                if (config_initialized && !host_timed_out) {
                    CCS_ConnectorState = CCS_UNPLUGGED;
                }
                break;
            case CCS_DISABLED:
                RELAY_Write(RELAY_CP, 0);
                CONN_SetState(Disabled);
                if (!FireAlarm_IsLatched() && (CONN.chargingError == CONN_NO_ERROR) && !host_timed_out){
                    CCS_ConnectorState = CCS_UNPLUGGED;
                }
                break;
            case CCS_UNPLUGGED:
                if (FireAlarm_IsLatched()) {
                    CCS_ConnectorState = CCS_DISABLED;
                    break;
                }
                RELAY_Write(RELAY_CP, 1);
                CONN_SetState(Unplugged);
                if ((cp_state_buffer == EV_STATE_B_CONN_PREP) || (cp_state_buffer == EV_STATE_C_CONN_ACTIVE)){
                    CCS_ConnectorState = CCS_AUTH_REQUIRED;
                }
                if (CONN.chargingError != CONN_NO_ERROR){
                    log_printf(LOG_ERR, "Charging error %d, state -> disabled\n", CONN.chargingError);
                    CCS_ConnectorState = CCS_DISABLED;
                }

                break;
            case CCS_AUTH_REQUIRED:
                if (FireAlarm_IsLatched()) {
                    CCS_ConnectorState = CCS_DISABLED;
                    break;
                }
                RELAY_Write(RELAY_CP, 1);
                CONN_SetState(AuthRequired);
                if(CONN.connControl == CMD_START){
                    log_printf(LOG_INFO, "Charging permitted, start charging\n");
                    CCS_ConnectorState = CCS_CONNECTED;
                }
                if (cp_state_buffer == EV_STATE_A_IDLE){
                    log_printf(LOG_INFO, "Car unplugged\n");
                    CCS_ConnectorState = CCS_UNPLUGGED;
                }
                break;
            case CCS_CONNECTED:
                if (FireAlarm_IsLatched()) {
                    CCS_ConnectorState = CCS_DISABLED;
                    break;
                }
                RELAY_Write(RELAY_CP, 1);
                if((CCS_EvseState < Preparing) || (CCS_EvseState == AuthRequired)) {
                    CONN_SetState(Preparing);
                } else {
                    CONN_SetState(CCS_EvseState);
                }
                if (cp_state_buffer == EV_STATE_A_IDLE){
                    log_printf(LOG_INFO, "Car unplugged\n");
                    CCS_ConnectorState = CCS_UNPLUGGED;
                }
                if(REPLUG > 0){
                    log_printf(LOG_INFO, "Replugging...\n");
                    CCS_ConnectorState = CCS_REPLUGGING;
                }
                break;
            case CCS_REPLUGGING:
                if (FireAlarm_IsLatched()) {
                    CCS_ConnectorState = CCS_DISABLED;
                    break;
                }
                RELAY_Write(RELAY_CP, 0);
                CONN_SetState(Replugging);
                if((int32_t)(HAL_GetTick() - replug_tick) > 1000){
                    replug_tick = HAL_GetTick();
                    if(REPLUG > 0){
                        if (REPLUG != 0xFF) REPLUG--;
                    } else {
                        log_printf(LOG_INFO, "Replugging finished, but car unplugged\n");
                        CCS_ConnectorState = CCS_UNPLUGGED;
                    }
                }

                if(REPLUG == 0){
                    if(cp_state_buffer == EV_STATE_B_CONN_PREP){
                        log_printf(LOG_INFO, "Replugging finished, car plugged, state -> auth required\n");
                        CCS_ConnectorState = CCS_AUTH_REQUIRED;
                    }
                }
                break;
    }

    // 10s timeout: enforce safe-state until host communication recovers.
    if (FireAlarm_IsLatched()) {
        CONN.EnableOutput = 0;
        CP_SetDuty(100);
        if (CCS_ConnectorState != CCS_DISABLED && CCS_ConnectorState != CCS_UNKNOWN) {
            CCS_ConnectorState = CCS_DISABLED;
        }
    } else if (host_timeout_stop) {
        CONN.EnableOutput = 0;
        CCS_EvseState = Unknown;
        CP_SetDuty(100);
        if (CCS_ConnectorState != CCS_DISABLED && CCS_ConnectorState != CCS_UNKNOWN) {
            CCS_ConnectorState = CCS_DISABLED;
        }
    } else {
        if (last_cmd == CMD_STOP) {
            CONN.EnableOutput = 0;
        } else {
            CONN.EnableOutput = ev_enable_output ? 1 : 0;
            if((CONN.EnableOutput == 0) && (CONN.connState == Preparing)){
                CONN.EnableOutput = 0;
            }
        }
    }

    if ((cp_state_buffer == EV_STATE_B_CONN_PREP) ||
        (cp_state_buffer == EV_STATE_C_CONN_ACTIVE) ||
        (cp_state_buffer == EV_STATE_D_CONN_ACT_VENT)) {
        CONN.EvConnected = 1;
    } else {
        CONN.EvConnected = 0;
    }
}

void CCS_Init(void){
    CP_Init();
    CP_SetDuty(100);
    CCS_MaxLoad.maxVoltage = PSU_MAX_VOLTAGE; // 1000V
	CCS_MaxLoad.minVoltage = PSU_MIN_VOLTAGE; //150V
	CCS_MaxLoad.maxCurrent = PSU_MAX_CURRENT*10; //100A
	CCS_MaxLoad.minCurrent = PSU_MIN_CURRENT*10; //1A
	CCS_MaxLoad.maxPower = PSU_MAX_POWER; //30000W
    uart3_last_packet_tick = HAL_GetTick();
    uart3_last_reinit_tick = uart3_last_packet_tick;
    uart3_arm_rx_or_log("Init");
    CCS_SendResetReason();
    log_printf(LOG_INFO, "CCS init\n");
}

ISR_FAST static uint16_t crc16_ibm(const uint8_t* data, uint16_t length) {
    uint16_t crc = 0xFFFFu;
    for (uint16_t i = 0; i < length; i++) {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 1u) {
                crc = (crc >> 1) ^ 0xA001u;
            } else {
                crc >>= 1;
            }
        }
    }
    return crc;
}

static uint16_t CCS_BuildPacket(uint8_t cmd, const void* payload, uint16_t payload_len, uint8_t* out, uint16_t out_max) {
    uint16_t total_len = (uint16_t)(1u + payload_len + 2u);
    if (total_len > out_max) return 0;

    out[0] = cmd;
    if (payload_len && payload != NULL) {
        memcpy(&out[1], payload, payload_len);
    }

    uint16_t crc = crc16_ibm(out, (uint16_t)(1u + payload_len));
    out[1u + payload_len] = (uint8_t)(crc & 0xFFu);
    out[1u + payload_len + 1u] = (uint8_t)((crc >> 8) & 0xFFu);

    return total_len;
}

static void CCS_SendPacket(uint8_t cmd, const void* payload, uint16_t payload_len) {
    uint16_t len = CCS_BuildPacket(cmd, payload, payload_len, tx_buffer, sizeof(tx_buffer));
    if (len > 0) {
        if (uart3_tx_busy) {
            memcpy(tx_pending_buffer, tx_buffer, len);
            tx_pending_len = len;
        } else {
            uart3_tx_busy = 1;
            if (HAL_UART_Transmit_DMA(&huart3, tx_buffer, len) != HAL_OK) {
                uart3_tx_busy = 0;
                CCS_LogUart3Error("UART3 TX DMA start failed");
            }
        }
    }
    last_cmd_sent = HAL_GetTick();
}

static void CCS_SendResetReason(void) {
    CCS_SendPacket(CMD_M2E_RESET, NULL, 0);
}

void CCS_SendEmergencyStop(void) {
    CCS_SendPacket(CMD_M2E_ESTOP, NULL, 0);
}

void CCS_SendStart(void) {
    CCS_SendPacket(CMD_M2E_START, NULL, 0);
}

static void CCS_CalculateEnergy(void) {
    static uint32_t lastTick = 0;
    uint32_t currentTick = HAL_GetTick();
    uint32_t elapsedTimeMs = currentTick - lastTick;
    lastTick = currentTick;

    CCS_Power = CONN.MeasuredVoltage * CONN.MeasuredCurrent / 10;
    CCS_EnergyWs += (CCS_Power * elapsedTimeMs) / 1000;

    if(CCS_EvseState == Unplugged) {
        CCS_EnergyWs = 0;
    }
    CCS_Energy = CCS_EnergyWs / 3600;
}

static void send_state(void) {
    CCS_CalculateEnergy();

    CCS_State.DutyCycle = CP_GetDuty();
    CCS_State.OutputEnabled = PSU0.CONT_enabled;
    CCS_State.MeasuredVoltage = (uint16_t)CONN.MeasuredVoltage;
    if (fake_500_voltage_mode) {
        CCS_State.MeasuredVoltage = FAKE_EVREQ_VOLTAGE_V;
    }
    CCS_State.MeasuredCurrent = (uint16_t)CONN.MeasuredCurrent;
    CCS_State.Power = CCS_Power;
    CCS_State.Energy = CCS_Energy;

    if(CCS_ConnectorState == CCS_CONNECTED){
        CCS_State.CpState = cp_state_buffer;
    } else {
        CCS_State.CpState = EV_STATE_A_IDLE;
    }

    CCS_State.MaxVoltage = CCS_MaxLoad.maxVoltage;
    CCS_State.MinVoltage = CCS_MaxLoad.minVoltage;
    CCS_State.MaxCurrent = CCS_MaxLoad.maxCurrent;
    CCS_State.MinCurrent = CCS_MaxLoad.minCurrent;
    CCS_State.MaxPower = CCS_MaxLoad.maxPower;
    CCS_State.IsolationValid = isolation_enable;
    CCS_State.IsolationResistance = 900000;

    CCS_SendPacket(CMD_M2E_STATE, &CCS_State, sizeof(CCS_State));
}

ISR_FAST static uint16_t expected_payload_len(uint8_t cmd) {
    switch (cmd) {
        case CMD_E2M_PWM_DUTY:           return sizeof(e2m_pwm_duty_t);
        case CMD_E2M_ENABLE_OUTPUT:      return sizeof(e2m_enable_output_t);
        case CMD_E2M_RESET:              return sizeof(e2m_reset_t);
        case CMD_E2M_ENABLE:             return sizeof(e2m_enable_t);
        case CMD_E2M_REPLUG:             return sizeof(e2m_replug_t);
        case CMD_E2M_SET_OUTPUT_VOLTAGE: return sizeof(e2m_set_output_t);
        case CMD_E2M_ISOLATION_CONTROL:  return sizeof(e2m_isolation_control_t);
        case CMD_E2M_EV_INFO:            return sizeof(CCS_EvInfo_t);
        case CMD_E2M_EVSE_STATE:         return sizeof(CONN_State_t);
        case CMD_E2M_KEEP_ALIVE:         return 0;
        default:                         return 0xFFFFu;
    }
}

ISR_FAST static void apply_command(uint8_t cmd, const uint8_t* payload, uint16_t payload_len) {
    (void)payload_len;
    last_host_seen = HAL_GetTick();
    everest_timed_out = 0;
    everest_timeout_warn_latched = 0;
    everest_timeout_stop_latched = 0;
    switch (cmd) {
        case CMD_E2M_PWM_DUTY: {
            const e2m_pwm_duty_t* p = (const e2m_pwm_duty_t*)payload;
            uint8_t duty = p->pwm_duty_percent;
            if (duty > 100) duty = 100;
            pwm_duty_percent = duty;
            if (CONN.connControl != CMD_FORCE_UNLOCK) {
                CP_SetDuty(duty);
            }
            break;
        }
        case CMD_E2M_ENABLE_OUTPUT: {
            const e2m_enable_output_t* p = (const e2m_enable_output_t*)payload;
            ev_enable_output = (p->enable_output != 0);
            break;
        }
        case CMD_E2M_RESET: {
            const e2m_reset_t* p = (const e2m_reset_t*)payload;
            if (p->reset) {
                log_printf(LOG_WARN, "Everest reset command\n");
                // CCS_SendResetReason();
                // HAL_Delay(10);
                // NVIC_SystemReset();
            }
            break;
        }
        case CMD_E2M_ENABLE: {
            const e2m_enable_t* p = (const e2m_enable_t*)payload;
            enabled = (p->enable != 0);
            (void)enabled;
            break;
        }
        case CMD_E2M_SET_OUTPUT_VOLTAGE: {
            const e2m_set_output_t* p = (const e2m_set_output_t*)payload;
            if (p->voltage_V == FAKE_EVREQ_VOLTAGE_V) {
                fake_500_voltage_mode = 1u;
                CONN.RequestedVoltage = FAKE_PSU_VOLTAGE_V;
                CONN.WantedCurrent = FAKE_PSU_CURRENT_0P1A;
            } else {
                fake_500_voltage_mode = 0u;
                CONN.RequestedVoltage = p->voltage_V;
                CONN.WantedCurrent = p->current_0p1A;
            }
            break;
        }
        case CMD_E2M_ISOLATION_CONTROL: {
            const e2m_isolation_control_t* p = (const e2m_isolation_control_t*)payload;
            isolation_enable = p->command;
            break;
        }
        case CMD_E2M_EV_INFO: {
            memcpy(&CCS_EvInfo, payload, sizeof(CCS_EvInfo_t));
            CONN.SOC = (uint8_t)(CCS_EvInfo.soc / 10);
            break;
        }
        case CMD_E2M_EVSE_STATE: {
            CCS_EvseState = (CONN_State_t)payload[0];
            break;
        }
        case CMD_E2M_REPLUG: {
            (void)payload;
            CP_SetDuty(pwm_duty_percent);
            break;
        }
        case CMD_E2M_KEEP_ALIVE: {
            last_host_seen = HAL_GetTick();
            break;
        }
        default:
            log_printf(LOG_WARN,
                       "UART3 RX warn: cmd 0x%02x CRC/len OK but no switch case (expected_payload vs apply_command)\n",
                       cmd);
            break;
    }
}

ISR_FAST static uint8_t process_received_packet(const uint8_t* packet, uint16_t packet_len) {
    if (packet_len < 3u) {
        if (packet_len == 0u) {
            log_printf(LOG_WARN, "UART3 RX drop: too_short len=0 (empty chunk)\n");
        } else if (packet_len == 1u) {
            log_printf(LOG_WARN, "UART3 RX drop: too_short len=1 b0=0x%02x\n", packet[0]);
        } else {
            log_printf(LOG_WARN, "UART3 RX drop: too_short len=2 b0=0x%02x b1=0x%02x\n",
                       packet[0], packet[1]);
        }
        return 0;
    }

    uint8_t cmd = packet[0];
    uint16_t payload_len = (uint16_t)(packet_len - 3u);

    uint16_t received_crc = (uint16_t)packet[packet_len - 2u] |
                            (uint16_t)packet[packet_len - 1u] << 8;

    uint16_t calculated_crc = crc16_ibm(packet, (uint16_t)(1u + payload_len));
    if (received_crc != calculated_crc) {
        log_printf(LOG_ERR,
                   "UART3 RX drop: crc_mismatch cmd=0x%02x total_len=%u payload_len=%u "
                   "crc_rx=0x%04x crc_calc=0x%04x\n",
                   cmd, (unsigned)packet_len, (unsigned)payload_len,
                   (unsigned)received_crc, (unsigned)calculated_crc);
        return 0;
    }

    uint16_t expected_len = expected_payload_len(cmd);
    if (expected_len == 0xFFFFu) {
        log_printf(LOG_WARN,
                   "UART3 RX drop: unknown_cmd cmd=0x%02x total_len=%u payload_len=%u\n",
                   cmd, (unsigned)packet_len, (unsigned)payload_len);
        return 0;
    }
    if (expected_len != payload_len) {
        log_printf(LOG_ERR,
                   "UART3 RX drop: len_mismatch cmd=0x%02x expected_payload=%u got_payload=%u "
                   "total_len=%u\n",
                   cmd, (unsigned)expected_len, (unsigned)payload_len, (unsigned)packet_len);
        return 0;
    }

    if (payload_len > 0) {
        apply_command(cmd, &packet[1], payload_len);
    } else {
        apply_command(cmd, NULL, 0);
    }

    return 1;
}

static void CCS_UART3_Watchdog(void) {
    const int32_t since_last_packet = (int32_t)(HAL_GetTick() - uart3_last_packet_tick);
    const int32_t since_last_reinit = (int32_t)(HAL_GetTick() - uart3_last_reinit_tick);

    if ((since_last_packet >= (int32_t)UART3_REINIT_TIMEOUT_MS) &&
        (since_last_reinit >= (int32_t)UART3_REINIT_TIMEOUT_MS) &&
        (huart3.RxState == HAL_UART_STATE_READY)) {
        uart3_arm_rx_or_log("Watchdog");
        CCS_LogUart3Error("UART3 watchdog rearm");
        uart3_last_reinit_tick = HAL_GetTick();
    }
}

static void CCS_LogUart3Error(const char *tag) {
    log_printf(LOG_ERR, "%s: err=0x%08lx g=%lu rx=%lu tx_busy=%u\n",
               tag,
               (unsigned long)HAL_UART_GetError(&huart3),
               (unsigned long)huart3.gState,
               (unsigned long)huart3.RxState,
               (unsigned)uart3_tx_busy);
}

