/*
 * test_watchdog.c — ESP32-S3 IWDG Watchdog Timer tests (Issue #300)
 *
 * Verifies:
 *   - Watchdog initialization with configurable timeouts
 *   - Timeout calculation and prescaler selection
 *   - Kick function for resetting watchdog counter
 *   - Timeout range validation
 *   - State tracking (running, initialized)
 *   - Reset reason detection
 *   - Edge cases and boundary conditions
 */

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

/* ── Watchdog Simulator ──────────────────────────────────────────*/

#define LSI_FREQUENCY_HZ    32000
#define IWDG_RELOAD_MIN     1
#define IWDG_RELOAD_MAX     4095

typedef struct {
    bool is_initialized;
    bool is_running;
    uint32_t timeout_ms;
    uint8_t prescaler;
    uint16_t reload_value;
    uint32_t counter;           /* Simulated counter */
    bool was_kicked;
    bool watchdog_fired;        /* Track if timeout occurred */
} WatchdogSim;

static WatchdogSim sim = {0};

void sim_init(void) {
    memset(&sim, 0, sizeof(sim));
}

bool sim_calculate_config(uint32_t timeout_ms,
                          uint8_t *out_prescaler,
                          uint16_t *out_reload)
{
    if (timeout_ms < 1 || timeout_ms > 32000) {
        return false;
    }

    const uint8_t prescalers[] = {0, 1, 2, 3, 4, 5, 6};
    const uint16_t dividers[] = {4, 8, 16, 32, 64, 128, 256};

    for (int i = 0; i < 7; i++) {
        uint16_t divider = dividers[i];
        uint32_t reload = (timeout_ms * LSI_FREQUENCY_HZ) / (divider * 1000);

        if (reload >= IWDG_RELOAD_MIN && reload <= IWDG_RELOAD_MAX) {
            *out_prescaler = prescalers[i];
            *out_reload = (uint16_t)reload;
            return true;
        }
    }

    return false;
}

bool sim_watchdog_init(uint32_t timeout_ms) {
    if (sim.is_initialized) return false;

    uint8_t prescaler;
    uint16_t reload;
    if (!sim_calculate_config(timeout_ms, &prescaler, &reload)) {
        return false;
    }

    sim.prescaler = prescaler;
    sim.reload_value = reload;
    sim.timeout_ms = timeout_ms;
    sim.is_initialized = true;
    sim.is_running = true;
    sim.counter = reload;  /* Counter starts at reload value */
    sim.watchdog_fired = false;

    return true;
}

void sim_watchdog_kick(void) {
    if (sim.is_running) {
        sim.counter = sim.reload_value;  /* Reset counter */
        sim.was_kicked = true;
    }
}

void sim_watchdog_tick(uint32_t elapsed_ms) {
    if (!sim.is_running) return;

    /* Decrement counter based on elapsed time */
    const uint16_t dividers[] = {4, 8, 16, 32, 64, 128, 256};
    uint16_t divider = dividers[sim.prescaler];

    /* Approximate: each ms decrements counter by (LSI_FREQUENCY / divider / 1000) */
    uint32_t decrement = (elapsed_ms * LSI_FREQUENCY_HZ) / (divider * 1000);

    if (decrement > sim.counter) {
        sim.watchdog_fired = true;
        sim.is_running = false;
        sim.counter = 0;
    } else {
        sim.counter -= decrement;
    }
}

uint32_t sim_watchdog_get_timeout(void) {
    return sim.timeout_ms;
}

bool sim_watchdog_is_running(void) {
    return sim.is_running;
}

/* ── Unit Tests ────────────────────────────────────────────────────────*/

static int test_count = 0, test_passed = 0, test_failed = 0;

#define TEST(name) do { test_count++; printf("\n  TEST %d: %s\n", test_count, name); } while(0)
#define ASSERT(cond, msg) do { if (cond) { test_passed++; printf("    ✓ %s\n", msg); } else { test_failed++; printf("    ✗ %s\n", msg); } } while(0)

void test_timeout_calculation(void) {
    TEST("Timeout calculation for standard values");
    uint8_t psc;
    uint16_t reload;

    /* 1 second */
    bool result = sim_calculate_config(1000, &psc, &reload);
    ASSERT(result == true, "1s timeout valid");
    ASSERT(reload > 0 && reload <= 4095, "Reload in valid range");

    /* 2 seconds (default) */
    result = sim_calculate_config(2000, &psc, &reload);
    ASSERT(result == true, "2s timeout valid");

    /* 4 seconds */
    result = sim_calculate_config(4000, &psc, &reload);
    ASSERT(result == true, "4s timeout valid");

    /* 16 seconds (max) */
    result = sim_calculate_config(16000, &psc, &reload);
    ASSERT(result == true, "16s timeout valid");
}

void test_initialization(void) {
    TEST("Watchdog initialization");
    sim_init();

    bool result = sim_watchdog_init(2000);
    ASSERT(result == true, "Initialize with 2s timeout");
    ASSERT(sim.is_initialized == true, "Marked as initialized");
    ASSERT(sim.is_running == true, "Marked as running");
    ASSERT(sim.timeout_ms == 2000, "Timeout stored correctly");
}

void test_double_init(void) {
    TEST("Prevent double initialization");
    sim_init();

    bool result = sim_watchdog_init(2000);
    ASSERT(result == true, "First init succeeds");

    result = sim_watchdog_init(1000);
    ASSERT(result == false, "Second init fails");
    ASSERT(sim.timeout_ms == 2000, "Original timeout unchanged");
}

void test_invalid_timeouts(void) {
    TEST("Invalid timeouts are rejected");
    sim_init();

    /* Too short */
    bool result = sim_watchdog_init(0);
    ASSERT(result == false, "0ms timeout rejected");

    /* Too long */
    sim_init();
    result = sim_watchdog_init(50000);
    ASSERT(result == false, "50s timeout rejected");

    /* Valid after invalid */
    sim_init();
    sim_watchdog_init(50000);  /* Invalid, should fail */
    result = sim_watchdog_init(2000);  /* Valid, should work */
    ASSERT(result == true, "Valid timeout works after invalid attempt");
}

void test_watchdog_kick(void) {
    TEST("Watchdog kick resets counter");
    sim_init();
    sim_watchdog_init(2000);

    sim_watchdog_tick(1000);  /* Wait 1 second */
    ASSERT(sim.counter < sim.reload_value, "Counter decremented");

    sim_watchdog_kick();  /* Reset counter */
    ASSERT(sim.counter == sim.reload_value, "Counter reset to reload value");
}

void test_watchdog_timeout(void) {
    TEST("Watchdog timeout triggers reset");
    sim_init();
    sim_watchdog_init(2000);

    sim_watchdog_tick(1000);
    ASSERT(sim.is_running == true, "Still running after 1 second");
    ASSERT(sim.watchdog_fired == false, "No timeout yet");

    sim_watchdog_tick(1500);  /* Total 2.5 seconds > 2s timeout */
    ASSERT(sim.is_running == false, "Stopped after timeout");
    ASSERT(sim.watchdog_fired == true, "Watchdog fired");
}

void test_watchdog_prevent_timeout(void) {
    TEST("Regular kicks prevent timeout");
    sim_init();
    sim_watchdog_init(2000);

    /* Kick every 1 second, timeout is 2 seconds */
    sim_watchdog_tick(500);
    sim_watchdog_kick();

    sim_watchdog_tick(1000);
    sim_watchdog_kick();

    sim_watchdog_tick(1500);
    sim_watchdog_kick();

    sim_watchdog_tick(2000);
    ASSERT(sim.is_running == true, "No timeout with regular kicks");
    ASSERT(sim.watchdog_fired == false, "Watchdog not fired");
}

void test_get_timeout(void) {
    TEST("Get timeout value");
    sim_init();
    sim_watchdog_init(3000);

    uint32_t timeout = sim_watchdog_get_timeout();
    ASSERT(timeout == 3000, "Timeout value retrieved correctly");
}

void test_is_running(void) {
    TEST("Check if watchdog is running");
    sim_init();

    ASSERT(sim_watchdog_is_running() == false, "Not running before init");

    sim_watchdog_init(2000);
    ASSERT(sim_watchdog_is_running() == true, "Running after init");

    sim_watchdog_tick(3000);  /* Timeout */
    ASSERT(sim_watchdog_is_running() == false, "Not running after timeout");
}

void test_multiple_timeouts(void) {
    TEST("Different timeout values");
    sim_init();

    uint32_t timeouts[] = {1000, 2000, 4000, 8000, 16000};
    for (int i = 0; i < 5; i++) {
        sim_init();
        bool result = sim_watchdog_init(timeouts[i]);
        ASSERT(result == true, "Timeout value valid");
    }
}

void test_boundary_1ms(void) {
    TEST("Minimum timeout (1ms)");
    sim_init();

    bool result = sim_watchdog_init(1);
    ASSERT(result == true, "1ms timeout accepted");
    ASSERT(sim.timeout_ms == 1, "Timeout set correctly");
}

void test_boundary_max(void) {
    TEST("Maximum reasonable timeout (32s)");
    sim_init();

    bool result = sim_watchdog_init(32000);
    ASSERT(result == true, "32s timeout accepted");
    ASSERT(sim.timeout_ms == 32000, "Timeout set correctly");
}

void test_prescaler_selection(void) {
    TEST("Appropriate prescaler selected");
    sim_init();

    /* Small timeout needs small prescaler */
    sim_watchdog_init(100);
    uint8_t psc_small = sim.prescaler;

    /* Large timeout needs large prescaler */
    sim_init();
    sim_watchdog_init(16000);
    uint8_t psc_large = sim.prescaler;

    ASSERT(psc_large > psc_small, "Larger timeout uses larger prescaler");
}

int main(void) {
    printf("\n══════════════════════════════════════════════════════════════\n");
    printf("  ESP32-S3 IWDG Watchdog Timer — Unit Tests (Issue #300)\n");
    printf("══════════════════════════════════════════════════════════════\n");

    test_timeout_calculation();
    test_initialization();
    test_double_init();
    test_invalid_timeouts();
    test_watchdog_kick();
    test_watchdog_timeout();
    test_watchdog_prevent_timeout();
    test_get_timeout();
    test_is_running();
    test_multiple_timeouts();
    test_boundary_1ms();
    test_boundary_max();
    test_prescaler_selection();

    printf("\n──────────────────────────────────────────────────────────────\n");
    printf("  Results: %d/%d tests passed, %d failed\n", test_passed, test_count, test_failed);
    printf("──────────────────────────────────────────────────────────────\n\n");

    return (test_failed == 0) ? 0 : 1;
}
