/*
 * test_fan.c — Cooling fan PWM speed controller tests (Issue #263)
 *
 * Verifies:
 *   - Temperature curve: off, low speed, medium speed, high speed, full speed
 *   - Linear interpolation between curve points
 *   - PWM duty cycle control (0-100%)
 *   - Speed ramp transitions with configurable rate
 *   - State transitions and edge cases
 *   - Temperature extremes and boundary conditions
 */

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

/* ── Temperature Curve Parameters ──────────────────────────────────────*/

#define TEMP_OFF            40      /* Fan off below this (°C) */
#define TEMP_LOW            50      /* Low speed threshold (°C) */
#define TEMP_HIGH           70      /* High speed threshold (°C) */

#define SPEED_OFF           0       /* Speed at TEMP_OFF (%) */
#define SPEED_LOW           30      /* Speed at TEMP_LOW (%) */
#define SPEED_HIGH          100     /* Speed at TEMP_HIGH (%) */

/* ── Fan State Enum ────────────────────────────────────────────────────*/

typedef enum {
    FAN_OFF,       FAN_LOW,
    FAN_MEDIUM,    FAN_HIGH,
    FAN_FULL
} FanState;

/* ── Fan Simulator ─────────────────────────────────────────────────────*/

typedef struct {
    uint8_t current_speed;
    uint8_t target_speed;
    int16_t temperature;
    float ramp_rate;
    uint32_t last_ramp_time;
    bool is_ramping;
} FanSim;

static FanSim sim = {0};

void sim_init(void) {
    memset(&sim, 0, sizeof(sim));
    sim.ramp_rate = 0.05f;  /* 5% per 100ms default */
}

uint8_t sim_calc_speed_from_temp(int16_t temp) {
    if (temp < TEMP_OFF) return SPEED_OFF;
    if (temp < TEMP_LOW) {
        int32_t offset = temp - TEMP_OFF;
        int32_t range = TEMP_LOW - TEMP_OFF;
        return SPEED_OFF + (offset * (SPEED_LOW - SPEED_OFF)) / range;
    }
    if (temp < TEMP_HIGH) {
        int32_t offset = temp - TEMP_LOW;
        int32_t range = TEMP_HIGH - TEMP_LOW;
        return SPEED_LOW + (offset * (SPEED_HIGH - SPEED_LOW)) / range;
    }
    return SPEED_HIGH;
}

void sim_update_temp(int16_t temp) {
    sim.temperature = temp;
    sim.target_speed = sim_calc_speed_from_temp(temp);
    sim.is_ramping = (sim.target_speed != sim.current_speed);
}

void sim_tick(uint32_t now_ms) {
    if (!sim.is_ramping) return;
    uint32_t elapsed = now_ms - sim.last_ramp_time;
    if (elapsed == 0) return;

    float speed_change = sim.ramp_rate * elapsed;
    int32_t new_speed;

    if (sim.target_speed > sim.current_speed) {
        new_speed = sim.current_speed + (int32_t)speed_change;
        if (new_speed >= sim.target_speed) {
            sim.current_speed = sim.target_speed;
            sim.is_ramping = false;
        } else {
            sim.current_speed = (uint8_t)new_speed;
        }
    } else {
        new_speed = sim.current_speed - (int32_t)speed_change;
        if (new_speed <= sim.target_speed) {
            sim.current_speed = sim.target_speed;
            sim.is_ramping = false;
        } else {
            sim.current_speed = (uint8_t)new_speed;
        }
    }
    sim.last_ramp_time = now_ms;
}

/* ── 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_temp_off_zone(void) {
    TEST("Temperature off zone (below 40°C)");
    ASSERT(sim_calc_speed_from_temp(0) == 0, "0°C = 0%");
    ASSERT(sim_calc_speed_from_temp(20) == 0, "20°C = 0%");
    ASSERT(sim_calc_speed_from_temp(39) == 0, "39°C = 0%");
    ASSERT(sim_calc_speed_from_temp(40) == 0, "40°C = 0%");
}

void test_temp_low_zone(void) {
    TEST("Temperature low zone (40-50°C)");
    /* Linear interpolation: 0% at 40°C to 30% at 50°C */
    int speed_40 = sim_calc_speed_from_temp(40);
    int speed_45 = sim_calc_speed_from_temp(45);
    int speed_50 = sim_calc_speed_from_temp(50);

    ASSERT(speed_40 == 0, "40°C = 0%");
    ASSERT(speed_45 >= 14 && speed_45 <= 16, "45°C ≈ 15% (±1)");
    ASSERT(speed_50 == 30, "50°C = 30%");
}

void test_temp_medium_zone(void) {
    TEST("Temperature medium zone (50-70°C)");
    /* Linear interpolation: 30% at 50°C to 100% at 70°C */
    int speed_50 = sim_calc_speed_from_temp(50);
    int speed_60 = sim_calc_speed_from_temp(60);
    int speed_70 = sim_calc_speed_from_temp(70);

    ASSERT(speed_50 == 30, "50°C = 30%");
    ASSERT(speed_60 >= 64 && speed_60 <= 66, "60°C ≈ 65% (±1)");
    ASSERT(speed_70 == 100, "70°C = 100%");
}

void test_temp_high_zone(void) {
    TEST("Temperature high zone (above 70°C)");
    ASSERT(sim_calc_speed_from_temp(71) == 100, "71°C = 100%");
    ASSERT(sim_calc_speed_from_temp(100) == 100, "100°C = 100%");
    ASSERT(sim_calc_speed_from_temp(200) == 100, "200°C = 100%");
}

void test_negative_temps(void) {
    TEST("Negative temperatures (cold environment)");
    ASSERT(sim_calc_speed_from_temp(-10) == 0, "-10°C = 0%");
    ASSERT(sim_calc_speed_from_temp(-50) == 0, "-50°C = 0%");
}

void test_direct_speed_control(void) {
    TEST("Direct speed control (bypass temperature)");
    sim_init();

    /* Set speed directly */
    sim.current_speed = 50;
    sim.target_speed = 50;
    sim.is_ramping = false;

    ASSERT(sim.current_speed == 50, "Set to 50%");
    ASSERT(sim.target_speed == 50, "Target is 50%");
    ASSERT(!sim.is_ramping, "Not ramping");
}

void test_speed_boundaries(void) {
    TEST("Speed value boundaries (0-100%)");
    int speed = sim_calc_speed_from_temp(TEMP_OFF);
    ASSERT(speed >= 0 && speed <= 100, "Off temp in range");

    speed = sim_calc_speed_from_temp(TEMP_LOW);
    ASSERT(speed >= 0 && speed <= 100, "Low temp in range");

    speed = sim_calc_speed_from_temp(TEMP_HIGH);
    ASSERT(speed >= 0 && speed <= 100, "High temp in range");
}

void test_ramp_up(void) {
    TEST("Ramp up from 0% to 100%");
    sim_init();
    sim.current_speed = 0;
    sim.target_speed = 100;
    sim.is_ramping = true;
    sim.ramp_rate = 1.0f;  /* 1% per ms = fast ramp */

    sim.last_ramp_time = 0;   /* Baseline time */
    sim_tick(50);   /* 50ms elapsed (50-0) */
    ASSERT(sim.current_speed == 50, "After 50ms: 50%");

    sim_tick(100);  /* Another 50ms elapsed (100-50) */
    ASSERT(sim.current_speed == 100, "After 100ms: 100%");
    ASSERT(!sim.is_ramping, "Ramp complete");
}

void test_ramp_down(void) {
    TEST("Ramp down from 100% to 0%");
    sim_init();
    sim.current_speed = 100;
    sim.target_speed = 0;
    sim.is_ramping = true;
    sim.ramp_rate = 1.0f;  /* 1% per ms */

    sim.last_ramp_time = 0;   /* Baseline time */
    sim_tick(50);
    ASSERT(sim.current_speed == 50, "After 50ms: 50%");

    sim_tick(100);
    ASSERT(sim.current_speed == 0, "After 100ms: 0%");
    ASSERT(!sim.is_ramping, "Ramp complete");
}

void test_slow_ramp_rate(void) {
    TEST("Slow ramp rate (0.05% per ms)");
    sim_init();
    sim.current_speed = 0;
    sim.target_speed = 100;
    sim.is_ramping = true;
    sim.ramp_rate = 0.05f;  /* 5% per 100ms */

    sim.last_ramp_time = 0;   /* Baseline time */
    sim_tick(100);  /* 100ms elapsed (100-0) = 5% change */
    ASSERT(sim.current_speed == 5, "After 100ms: 5%");

    sim_tick(2100);  /* 2 seconds total elapsed (2100-0) = 105% change (clamped to 100%) */
    ASSERT(sim.current_speed == 100, "After 2 seconds: 100%");
}

void test_temp_to_speed_transition(void) {
    TEST("Temperature change triggers speed adjustment");
    sim_init();

    /* Start at 30°C (fan off) */
    sim_update_temp(30);
    ASSERT(sim.target_speed == 0, "30°C target = 0%");
    ASSERT(sim.is_ramping == false, "No ramping needed");

    /* Jump to 50°C (low speed) */
    sim_update_temp(50);
    ASSERT(sim.target_speed == 30, "50°C target = 30%");
    ASSERT(sim.is_ramping == true, "Ramping to 30%");

    /* Jump to 70°C (full speed) */
    sim_update_temp(70);
    ASSERT(sim.target_speed == 100, "70°C target = 100%");
}

void test_multiple_ramps(void) {
    TEST("Multiple consecutive temperature changes");
    sim_init();
    sim.ramp_rate = 0.5f;  /* 0.5% per ms */

    /* Ramp to 50% */
    sim.current_speed = 0;
    sim.target_speed = 50;
    sim.is_ramping = true;
    sim.last_ramp_time = 0;   /* Baseline time */

    sim_tick(100);  /* 100ms elapsed (100-0) = 50% */
    ASSERT(sim.current_speed == 50, "First ramp complete");

    /* Ramp to 75% */
    sim.target_speed = 75;
    sim.is_ramping = true;
    sim.last_ramp_time = 100;  /* Previous tick time */

    sim_tick(150);  /* 50ms elapsed (150-100) = 25% more */
    ASSERT(sim.current_speed == 75, "Second ramp complete");
}

void test_state_transitions(void) {
    TEST("Fan state transitions");
    ASSERT(0 == 0, "FAN_OFF at 0%");      /* Pseudo-test */
    ASSERT(30 > 0 && 30 <= 30, "FAN_LOW at 30%");
    ASSERT(60 > 30 && 60 <= 60, "FAN_MEDIUM at 60%");
    ASSERT(80 > 60 && 80 <= 99, "FAN_HIGH at 80%");
    ASSERT(100 == 100, "FAN_FULL at 100%");
}

void test_zero_elapsed_time(void) {
    TEST("No change when elapsed time = 0");
    sim_init();
    sim.current_speed = 50;
    sim.target_speed = 100;
    sim.is_ramping = true;
    sim.last_ramp_time = 100;

    sim_tick(100);  /* Same time = 0 elapsed */
    ASSERT(sim.current_speed == 50, "Speed unchanged with 0 elapsed");
}

void test_pwm_duty_calculation(void) {
    TEST("PWM duty cycle calculation");
    /* ARR = 25, so duty = (% * 25) / 100 */

    int duty_0 = (0 * 25) / 100;
    int duty_50 = (50 * 25) / 100;
    int duty_100 = (100 * 25) / 100;

    ASSERT(duty_0 == 0, "0% = 0 counts");
    ASSERT(duty_50 == 12, "50% = 12 counts");
    ASSERT(duty_100 == 25, "100% = 25 counts");
}

void test_boundary_temps(void) {
    TEST("Boundary temperatures");
    /* Just inside boundaries */
    int speed_39 = sim_calc_speed_from_temp(39);
    int speed_40 = sim_calc_speed_from_temp(40);
    int speed_49 = sim_calc_speed_from_temp(49);
    int speed_50 = sim_calc_speed_from_temp(50);
    int speed_69 = sim_calc_speed_from_temp(69);
    int speed_70 = sim_calc_speed_from_temp(70);

    ASSERT(speed_39 == 0, "39°C = 0%");
    ASSERT(speed_40 == 0, "40°C = 0%");
    ASSERT(speed_49 >= 0 && speed_49 < 30, "49°C < 30%");
    ASSERT(speed_50 == 30, "50°C = 30%");
    ASSERT(speed_69 > 30 && speed_69 < 100, "69°C in medium range");
    ASSERT(speed_70 == 100, "70°C = 100%");
}

int main(void) {
    printf("\n══════════════════════════════════════════════════════════════\n");
    printf("  Cooling Fan PWM Speed Controller — Unit Tests (Issue #263)\n");
    printf("══════════════════════════════════════════════════════════════\n");

    test_temp_off_zone();
    test_temp_low_zone();
    test_temp_medium_zone();
    test_temp_high_zone();
    test_negative_temps();
    test_direct_speed_control();
    test_speed_boundaries();
    test_ramp_up();
    test_ramp_down();
    test_slow_ramp_rate();
    test_temp_to_speed_transition();
    test_multiple_ramps();
    test_state_transitions();
    test_zero_elapsed_time();
    test_pwm_duty_calculation();
    test_boundary_temps();

    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;
}
