#include "fan_manager.h"

#include <assert.h>
#include <stdio.h>

typedef struct {
    bool power;
    unsigned duty;
    unsigned events[64];
    unsigned event_count;
    unsigned fail_power_call;
    unsigned fail_duty_call;
    unsigned power_calls;
    unsigned duty_calls;
} fake_io_t;

static int set_power(void *context, bool enabled)
{
    fake_io_t *fake = context;
    ++fake->power_calls;
    fake->events[fake->event_count++] = enabled ? 2U : 1U;
    if (fake->power_calls == fake->fail_power_call) {
        return -1;
    }
    fake->power = enabled;
    return 0;
}

static int set_duty(void *context, uint16_t duty)
{
    fake_io_t *fake = context;
    ++fake->duty_calls;
    fake->events[fake->event_count++] = 3U;
    if (fake->duty_calls == fake->fail_duty_call) {
        return -1;
    }
    fake->duty = duty;
    return 0;
}

static vacuum_fan_io_t make_io(fake_io_t *fake)
{
    vacuum_fan_io_t io = {fake, set_power, set_duty};
    return io;
}

static vacuum_fan_input_t valid_request(void)
{
    vacuum_fan_input_t input = {
        .sample_age_ms = 10U,
        .now_ms = UINT32_MAX - 99U,
        .vbus_mv = 12000U,
        .fan_current_ma = 0U,
        .tach_edges_100ms = 0U,
        .control_duty_permille = 600U,
        .measurement_valid = true,
        .fan_profile_valid = true,
        .run_requested = true,
    };
    return input;
}

int main(void)
{
    const vacuum_fan_limits_t limits = {11000U, 13000U, 2400U, 100U, 2U, 500U};
    fake_io_t fake = {0};
    vacuum_fan_io_t io = make_io(&fake);
    vacuum_fan_manager_t manager;
    vacuum_fan_input_t input = valid_request();

    vacuum_fan_manager_init(&manager, &io);
    assert(!fake.power && fake.duty == 0U);
    assert(!manager.fan_power_enabled && manager.fan_power_command_confirmed);

    /* A live, validated source configures PWM before the fan power FET turns on. */
    fake.event_count = 0U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OK);
    assert(fake.power && fake.duty == 600U);
    assert(manager.fan_start_time_valid && manager.fan_started_at_ms == input.now_ms);
    assert(fake.event_count == 2U && fake.events[0] == 3U && fake.events[1] == 2U);

    /* The calibrated grace period allows spin-up, then missing tach pulses fail closed. */
    input.now_ms += 400U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OK);
    input.now_ms += 100U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_TACH_STALL);
    assert(!fake.power && manager.fault_latched && !manager.fan_start_time_valid);
    input.run_requested = false;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_NOT_READY);

    /* A calibrated tach rate keeps the fan active beyond the same grace period. */
    input = valid_request();
    input.tach_edges_100ms = 2U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OK);
    input.now_ms += 500U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OK);
    input.run_requested = false;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_NOT_READY);

    /* A 5V PDO fallback, stale ADC snapshot, or unqualified blower fails closed. */
    input = valid_request();
    input.vbus_mv = 5000U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_UNDERVOLTAGE);
    assert(!fake.power && manager.fault_latched);
    input = valid_request();
    input.run_requested = false;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_NOT_READY);
    input = valid_request();
    input.sample_age_ms = 101U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_STALE_SAMPLE);
    input = valid_request();
    input.run_requested = false;
    (void)vacuum_fan_manager_step(&manager, &input, &limits, &io);
    input = valid_request();
    input.fan_profile_valid = false;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_NOT_READY);

    {
        vacuum_fan_limits_t uncalibrated_tach_limits = limits;
        input = valid_request();
        uncalibrated_tach_limits.min_tach_edges_100ms = 0U;
        assert(vacuum_fan_manager_step(&manager, &input, &uncalibrated_tach_limits, &io) ==
               VACUUM_FAN_NOT_READY);
        assert(!fake.power && manager.fault_latched);
    }

    /* Overcurrent, overvoltage and callback failures latch the switch off. */
    input = valid_request();
    input.run_requested = false;
    (void)vacuum_fan_manager_step(&manager, &input, &limits, &io);
    input = valid_request();
    input.vbus_mv = 13001U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OVERVOLTAGE);
    input = valid_request();
    input.run_requested = false;
    (void)vacuum_fan_manager_step(&manager, &input, &limits, &io);
    input = valid_request();
    input.fan_current_ma = 2401U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OVERCURRENT);
    input = valid_request();
    input.run_requested = false;
    (void)vacuum_fan_manager_step(&manager, &input, &limits, &io);
    input = valid_request();
    fake.fail_power_call = fake.power_calls + 1U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_IO_FAILURE);
    assert(!manager.fan_power_enabled && manager.fault_latched);
    input.run_requested = false;
    (void)vacuum_fan_manager_step(&manager, &input, &limits, &io);
    input = valid_request();
    fake.fail_duty_call = fake.duty_calls + 1U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_IO_FAILURE);
    assert(!manager.fan_power_enabled && !fake.power && manager.fault_latched);

    /* If the physical-off callback fails during a hard fault, preserve the last
     * confirmed state and expose the uncertainty until an explicit stop reset. */
    input = valid_request();
    input.run_requested = false;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_NOT_READY);
    input = valid_request();
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OK);
    input.vbus_mv = 13001U;
    fake.fail_power_call = fake.power_calls + 1U;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OFF_UNCONFIRMED);
    assert(fake.power && manager.fan_power_enabled);
    assert(!manager.fan_power_command_confirmed && manager.fault_latched);
    input = valid_request();
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_OFF_UNCONFIRMED);
    assert(!fake.power && !manager.fan_power_enabled);
    assert(manager.fan_power_command_confirmed && manager.fault_latched);
    input.run_requested = false;
    assert(vacuum_fan_manager_step(&manager, &input, &limits, &io) == VACUUM_FAN_NOT_READY);
    assert(!manager.fault_latched);

    puts("PASS: fan policy, tach stall trip, startup grace, shutdown retries and unconfirmed-off reporting.");
    return 0;
}
