#include "lamp_ui_dispatch.h"

#include <stdio.h>
#include <stdlib.h>

static unsigned checks;

#define CHECK(expr) do { \
    ++checks; \
    if (!(expr)) { \
        fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #expr); \
        exit(EXIT_FAILURE); \
    } \
} while (0)

static void configure_guard(lg_guard *guard, uint64_t session_id,
                            uint32_t now_ms, lg_inputs inputs) {
    lg_config config = {0};
    config.channels = 2u;
    config.max_run_ms = LG_MANUAL_MAX_RUN_MS;
    config.ticket_lifetime_ms = 900000u;
    config.max_poll_gap_ms = 900000u;
    config.channel_input_mw[0] = 100u;
    config.channel_input_mw[1] = 100u;
    config.fixed_input_mw = 100u;
    config.input_budget_mw = 500u;
    config.calibration_id = 1u;
    CHECK(lg_init(guard, &config, session_id, now_ms));
    lg_poll(guard, now_ms, inputs);
}

static lamp_ui_ack send(lamp_ui_dispatcher *dispatcher, uint64_t session,
                        uint32_t sequence, lamp_ui_button button,
                        uint32_t now_ms) {
    const lamp_ui_event event = {session, sequence, button};
    lamp_ui_ack ack = {0};
    CHECK(lamp_ui_dispatcher_handle(dispatcher, &event, now_ms, &ack));
    return ack;
}

int main(void) {
    const uint64_t session = UINT64_C(0x123456789ABCDEF0);
    const lg_inputs safe_inputs = {true, true, true, true};
    lg_inputs unsafe_inputs = safe_inputs;
    lg_guard guard = {0};
    lamp_ui_dispatcher dispatcher;

    CHECK(lamp_ui_dispatcher_init(&dispatcher, &guard, session, LG_OFF));
    lamp_ui_ack ack = send(&dispatcher, session + 1u, 1u,
                           LUI_BUTTON_CANCEL, 10u);
    CHECK(ack.status == LUID_ACK_BAD_SESSION);
    CHECK(!guard.armed && guard.mode == LG_OFF);

    /* Mode selection is acknowledged only after stopping, even before setup. */
    ack = send(&dispatcher, session, 1u,
               LUI_BUTTON_LONG_PRESS_MANUAL, 20u);
    CHECK(ack.status == LUID_ACK_ACCEPTED);
    CHECK(ack.selected_mode == LG_MANUAL && !ack.running);
    CHECK(!guard.armed && !guard.output_enable);

    /* The real guard refuses to arm before validated configuration exists. */
    ack = send(&dispatcher, session, 2u, LUI_BUTTON_MANUAL_60, 30u);
    CHECK(ack.status == LUID_ACK_NOT_READY && !ack.running);
    configure_guard(&guard, session, 40u, safe_inputs);

    /* Exact retry preserves the prior rejection; a new sequence is required. */
    ack = send(&dispatcher, session, 2u, LUI_BUTTON_MANUAL_60, 50u);
    CHECK(ack.duplicate && ack.status == LUID_ACK_NOT_READY);
    CHECK(!guard.armed);
    ack = send(&dispatcher, session, 3u, LUI_BUTTON_MANUAL_60, 100u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && ack.running);
    CHECK(guard.mode == LG_MANUAL && guard.armed && !guard.output_enable);
    CHECK(ack.remaining_ms == 60000u);

    /* Retry returns current countdown without restarting the guard timer. */
    ack = send(&dispatcher, session, 3u, LUI_BUTTON_MANUAL_60, 1000u);
    CHECK(ack.duplicate && ack.status == LUID_ACK_ACCEPTED && ack.running);
    CHECK(ack.remaining_ms == 59100u);

    /* Changing a preset stops the previous run before arming the new timer. */
    ack = send(&dispatcher, session, 4u, LUI_BUTTON_MANUAL_120, 1100u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && ack.running);
    CHECK(guard.mode == LG_MANUAL && guard.run_duration_ms == 120000u);
    CHECK(ack.remaining_ms == 120000u && !guard.output_enable);

    ack = send(&dispatcher, session, 5u,
               LUI_BUTTON_LONG_PRESS_MANUAL, 1200u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && !ack.running);
    CHECK(guard.mode == LG_OFF && !guard.armed && !guard.output_enable);

    /* The product recipe policy is exercised with the real program and guard. */
    CHECK(lamp_ui_dispatcher_init(&dispatcher, &guard, session + 1u, LG_OFF));
    ack = send(&dispatcher, session + 1u, 1u, LUI_BUTTON_BASE_COLOR, 1300u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && ack.selected_mode == LG_AI);
    CHECK(guard.mode == LG_AI && guard.armed && guard.run_duration_ms == 45000u);
    CHECK(!guard.output_enable);
    ack = send(&dispatcher, session + 1u, 2u, LUI_BUTTON_DARK_GEL, 1400u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && ack.selected_mode == LG_MANUAL);
    CHECK(guard.mode == LG_MANUAL && guard.armed &&
          guard.run_duration_ms == LG_MANUAL_MAX_RUN_MS);
    ack = send(&dispatcher, session + 1u, 3u, LUI_BUTTON_TOP_COAT, 1500u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && ack.running);
    CHECK(guard.mode == LG_MANUAL && guard.run_duration_ms == 120000u);

    /* Real guard faults reject starts; cancel remains accepted and latched. */
    unsafe_inputs.temperature_ok = false;
    lg_poll(&guard, 1600u, unsafe_inputs);
    CHECK(guard.mode == LG_FAULT && !guard.armed && !guard.output_enable);
    ack = send(&dispatcher, session + 1u, 4u, LUI_BUTTON_BASE_COLOR, 1700u);
    CHECK(ack.status == LUID_ACK_INVALID_COMMAND && guard.mode == LG_FAULT);
    ack = send(&dispatcher, session + 1u, 5u, LUI_BUTTON_CANCEL, 1800u);
    CHECK(ack.status == LUID_ACK_ACCEPTED && guard.mode == LG_FAULT);
    ack = send(&dispatcher, session + 1u, 4u, LUI_BUTTON_CANCEL, 1900u);
    CHECK(ack.status == LUID_ACK_STALE_SEQUENCE);
    ack = send(&dispatcher, session + 1u, 6u, LUI_BUTTON_INVALID, 2000u);
    CHECK(ack.status == LUID_ACK_INVALID_COMMAND);

    /* Unsigned wrap is supported; zero remains reserved. */
    configure_guard(&guard, session + 2u, 3000u, safe_inputs);
    CHECK(lamp_ui_dispatcher_init(&dispatcher, &guard, session + 2u, LG_OFF));
    ack = send(&dispatcher, session + 2u, UINT32_MAX,
               LUI_BUTTON_CANCEL, 3010u);
    CHECK(ack.status == LUID_ACK_ACCEPTED);
    ack = send(&dispatcher, session + 2u, 1u, LUI_BUTTON_CANCEL, 3020u);
    CHECK(ack.status == LUID_ACK_ACCEPTED);
    const lamp_ui_event zero_sequence = {
        session + 2u, 0u, LUI_BUTTON_CANCEL
    };
    CHECK(!lamp_ui_dispatcher_handle(&dispatcher, &zero_sequence, 3030u, &ack));

    printf("PASS: lamp_ui_dispatch integrated %u assertions\n", checks);
    return EXIT_SUCCESS;
}
