#include "ai_wire.h"
#include "lamp_guard_wire_adapter.h"
#include "lamp_ticket_dispatch.h"

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

typedef struct {
    uint8_t packet[AIW_HEADER_BYTES + AIW_TICKET_PAYLOAD_BYTES + AIW_CRC_BYTES];
    size_t length;
    unsigned calls;
    bool accept;
} fake_link;

static const lg_inputs good_inputs = {true, true, true, true};

static lg_config test_config(void)
{
    lg_config cfg = {0};
    cfg.channels = 61u;
    cfg.max_run_ms = 1000u;
    cfg.ticket_lifetime_ms = 200u;
    cfg.max_poll_gap_ms = 20u;
    cfg.calibration_id = 77u;
    cfg.fixed_input_mw = 1000u;
    cfg.input_budget_mw = 20000u;
    for (uint16_t i = 0u; i < LG_MAX_CHANNELS; ++i) cfg.channel_input_mw[i] = 200u;
    return cfg;
}

static void setup_armed_ai_guard(lg_guard *guard)
{
    const lg_config cfg = test_config();
    assert(lg_init(guard, &cfg, UINT64_C(0x1020304050607080), 0u));
    lg_poll(guard, 0u, good_inputs);
    assert(lg_arm(guard, LG_AI, 900u, 0u));
}

static bool fake_send(void *context, const uint8_t *packet, size_t length)
{
    fake_link *link = (fake_link *)context;
    assert(link != NULL);
    ++link->calls;
    if (!link->accept || packet == NULL || length > sizeof(link->packet)) return false;
    memcpy(link->packet, packet, length);
    link->length = length;
    return true;
}

static void test_not_ready_does_not_send(void)
{
    lg_guard guard = {0};
    fake_link link = {0};
    assert(lamp_ticket_dispatch_issue(&guard, 0u, fake_send, &link) == LAMP_TICKET_DISPATCH_NOT_READY);
    assert(link.calls == 0u && !guard.pending_ticket && !guard.output_enable);
}

static void test_ticket_request_and_matching_result(void)
{
    lg_guard guard;
    fake_link link = {.accept = true};
    aiw_ticket ticket = {0};
    aiw_result result = {0};
    setup_armed_ai_guard(&guard);
    assert(lamp_ticket_dispatch_issue(&guard, 1u, fake_send, &link) == LAMP_TICKET_DISPATCH_QUEUED);
    assert(link.calls == 1u && guard.pending_ticket);
    assert(aiw_decode_ticket(link.packet, link.length, &ticket));
    assert(ticket.session_id == guard.session_id);
    assert(ticket.ticket_id == guard.ticket_counter);
    assert(ticket.calibration_id == guard.cfg.calibration_id);
    assert(ticket.channels == guard.cfg.channels);
    assert(ticket.min_source_frame_seq == 1u);
    result.session_id = ticket.session_id;
    result.ticket_id = ticket.ticket_id;
    result.calibration_id = ticket.calibration_id;
    result.channels = ticket.channels;
    result.capture_baseline_seq = 0u;
    result.source_frame_seq = ticket.min_source_frame_seq;
    result.status_flags = AIW_RESULT_FLAG_HAND_PRESENT;
    result.pwm[0] = 64u;
    assert(lg_accept_ai_decoded_result(&guard, &result, 2u));
    assert(guard.output_enable && guard.pwm[0] == 64u);
}

static void test_send_failure_revokes_ticket_and_output(void)
{
    lg_guard guard;
    fake_link link = {.accept = true};
    aiw_ticket ticket = {0};
    aiw_result result = {0};
    setup_armed_ai_guard(&guard);
    assert(lamp_ticket_dispatch_issue(&guard, 1u, fake_send, &link) == LAMP_TICKET_DISPATCH_QUEUED);
    assert(aiw_decode_ticket(link.packet, link.length, &ticket));
    result.session_id = ticket.session_id;
    result.ticket_id = ticket.ticket_id;
    result.calibration_id = ticket.calibration_id;
    result.channels = ticket.channels;
    result.source_frame_seq = ticket.min_source_frame_seq;
    result.status_flags = AIW_RESULT_FLAG_HAND_PRESENT;
    result.pwm[0] = 32u;
    assert(lg_accept_ai_decoded_result(&guard, &result, 2u));
    assert(guard.output_enable);
    link.accept = false;
    assert(lamp_ticket_dispatch_issue(&guard, 3u, fake_send, &link) == LAMP_TICKET_DISPATCH_SEND_FAILED);
    assert(link.calls == 2u);
    assert(!guard.armed && !guard.pending_ticket && !guard.output_enable);
    for (uint16_t i = 0u; i < LG_MAX_CHANNELS; ++i) assert(guard.pwm[i] == 0u);
}

static void test_missing_link_does_not_issue_ticket(void)
{
    lg_guard guard;
    setup_armed_ai_guard(&guard);
    assert(lamp_ticket_dispatch_issue(&guard, 1u, NULL, NULL) == LAMP_TICKET_DISPATCH_INVALID_ARGUMENT);
    assert(!guard.pending_ticket && guard.ticket_counter == 0u);
}

int main(void)
{
    test_not_ready_does_not_send();
    test_ticket_request_and_matching_result();
    test_send_failure_revokes_ticket_and_output();
    test_missing_link_does_not_issue_ticket();
    puts("PASS: guard ticket becomes AI_REQUEST; matching result accepted; TX failure revokes ticket and output.");
    return 0;
}
