/*
 * test_can_watchdog.c — Unit tests for CAN bus watchdog and error recovery (Issue #694).
 *
 * Build (host, no hardware):
 *   gcc -I include -I test/stubs -DTEST_HOST \
 *       -o /tmp/test_can_watchdog src/can_driver.c test/test_can_watchdog.c
 *
 * All timing driven through now_ms argument passed to can_driver_watchdog_tick().
 * ESR state injected via can_driver_inject_esr().
 */

/* Include implementation directly (pulls stm32 stub via -I test/stubs) */
#include "../src/can_driver.c"

/* ---- Test framework ---- */
#include <stdio.h>
#include <string.h>

static int g_pass = 0;
static int g_fail = 0;

#define ASSERT(cond, msg) \
    do { \
        if (cond) { g_pass++; } \
        else { g_fail++; printf("FAIL [%s:%d] %s\n", __FILE__, __LINE__, msg); } \
    } while (0)

/* HAL_GetTick stub */
uint32_t HAL_GetTick(void) { return 0u; }

/* Reset driver state between tests */
static void _reset(void)
{
    can_driver_inject_esr(0u);
    can_driver_init();
}

/* ---- Tests ---- */

static void test_nominal_no_errors(void)
{
    _reset();
    can_error_state_t st = can_driver_watchdog_tick(0u);
    ASSERT(st == CAN_ERR_NOMINAL, "clean ESR: state is NOMINAL");

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.restart_count == 0u, "nominal: restart_count 0");
    ASSERT(wd.busoff_count  == 0u, "nominal: busoff_count 0");
}

static void test_errwarn_detection(void)
{
    _reset();
    can_driver_inject_esr(CAN_ESR_EWGF);
    can_error_state_t st = can_driver_watchdog_tick(1000u);
    ASSERT(st == CAN_ERR_WARNING, "EWGF set: state is WARNING");

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.errwarn_count == 1u, "errwarn_count incremented");
}

static void test_errwarn_cleared_returns_nominal(void)
{
    _reset();
    /* Enter warning state */
    can_driver_inject_esr(CAN_ESR_EWGF);
    can_driver_watchdog_tick(100u);

    /* Clear ESR - should de-escalate */
    can_driver_inject_esr(0u);
    can_error_state_t st = can_driver_watchdog_tick(200u);
    ASSERT(st == CAN_ERR_NOMINAL, "ESR cleared: de-escalate to NOMINAL");
}

static void test_errpassive_detection(void)
{
    _reset();
    can_driver_inject_esr(CAN_ESR_EPVF);
    can_error_state_t st = can_driver_watchdog_tick(500u);
    ASSERT(st == CAN_ERR_ERROR_PASSIVE, "EPVF set: state is ERROR_PASSIVE");

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.errpassive_count == 1u, "errpassive_count incremented");
}

static void test_busoff_detection(void)
{
    _reset();
    can_driver_inject_esr(CAN_ESR_BOFF);
    can_error_state_t st = can_driver_watchdog_tick(1000u);
    ASSERT(st == CAN_ERR_BUS_OFF, "BOFF set: state is BUS_OFF");

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.busoff_count == 1u, "busoff_count incremented");
    ASSERT(wd.busoff_ms == 1000u, "busoff_ms recorded");
}

static void test_busoff_sets_stats_bus_off(void)
{
    _reset();
    can_driver_inject_esr(CAN_ESR_BOFF);
    can_driver_watchdog_tick(500u);

    can_stats_t cs;
    can_driver_get_stats(&cs);
    ASSERT(cs.bus_off == 1u, "bus-off: stats.bus_off = 1");
}

static void test_busoff_no_restart_before_timeout(void)
{
    _reset();
    can_driver_inject_esr(CAN_ESR_BOFF);
    can_driver_watchdog_tick(0u);

    /* Call again just before CAN_WDOG_RESTART_MS */
    can_error_state_t st = can_driver_watchdog_tick(CAN_WDOG_RESTART_MS - 1u);
    ASSERT(st == CAN_ERR_BUS_OFF, "before restart timeout: still BUS_OFF");

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.restart_count == 0u, "no restart yet");
}

static void test_busoff_auto_restart_after_timeout(void)
{
    _reset();
    /* Inject bus-off at t=0 */
    can_driver_inject_esr(CAN_ESR_BOFF);
    can_driver_watchdog_tick(0u);

    /* After restart timeout, HAL_CAN_Stop/Start clears s_test_esr internally */
    can_error_state_t st = can_driver_watchdog_tick(CAN_WDOG_RESTART_MS);
    ASSERT(st == CAN_ERR_NOMINAL, "after restart timeout: state is NOMINAL");

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.restart_count == 1u, "restart_count incremented to 1");
    ASSERT(wd.busoff_ms == 0u,    "busoff_ms cleared after restart");
}

static void test_busoff_stats_cleared_after_restart(void)
{
    _reset();
    can_driver_inject_esr(CAN_ESR_BOFF);
    can_driver_watchdog_tick(0u);
    can_driver_watchdog_tick(CAN_WDOG_RESTART_MS);

    can_stats_t cs;
    can_driver_get_stats(&cs);
    ASSERT(cs.bus_off == 0u, "after restart: stats.bus_off cleared");
}

static void test_busoff_count_only_increments_once_per_event(void)
{
    _reset();
    can_driver_inject_esr(CAN_ESR_BOFF);
    /* Call tick multiple times while still in bus-off */
    can_driver_watchdog_tick(0u);
    can_driver_watchdog_tick(10u);
    can_driver_watchdog_tick(50u);

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.busoff_count == 1u, "repeated BOFF ticks: count stays 1");
}

static void test_multiple_restart_cycles(void)
{
    _reset();
    /* Cycle 1 */
    can_driver_inject_esr(CAN_ESR_BOFF);
    can_driver_watchdog_tick(0u);
    can_driver_watchdog_tick(CAN_WDOG_RESTART_MS);  /* restart */

    /* Cycle 2: inject bus-off again */
    can_driver_inject_esr(CAN_ESR_BOFF);
    can_driver_watchdog_tick(CAN_WDOG_RESTART_MS + 1u);
    can_driver_watchdog_tick(CAN_WDOG_RESTART_MS * 2u + 1u);  /* restart */

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.restart_count == 2u, "two restart cycles: restart_count = 2");
    ASSERT(wd.busoff_count  == 2u, "two restart cycles: busoff_count = 2");
}

static void test_tec_rec_extracted(void)
{
    _reset();
    /* Inject ESR with TEC=0xAB (bits 23:16) and REC=0xCD (bits 31:24) */
    uint32_t fake_esr = (0xABu << 16) | (0xCDu << 24);
    can_driver_inject_esr(fake_esr);
    can_driver_watchdog_tick(100u);

    can_wdog_t wd;
    can_driver_get_wdog(&wd);
    ASSERT(wd.tec == 0xABu, "TEC extracted from ESR correctly");
    ASSERT(wd.rec == 0xCDu, "REC extracted from ESR correctly");
}

/* ---- main ---- */
int main(void)
{
    printf("=== test_can_watchdog ===\n");

    test_nominal_no_errors();
    test_errwarn_detection();
    test_errwarn_cleared_returns_nominal();
    test_errpassive_detection();
    test_busoff_detection();
    test_busoff_sets_stats_bus_off();
    test_busoff_no_restart_before_timeout();
    test_busoff_auto_restart_after_timeout();
    test_busoff_stats_cleared_after_restart();
    test_busoff_count_only_increments_once_per_event();
    test_multiple_restart_cycles();
    test_tec_rec_extracted();

    printf("\nResults: %d passed, %d failed\n", g_pass, g_fail);
    return g_fail ? 1 : 0;
}
