#include "ai_wire.h"
#include "lamp_guard_wire_adapter.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>

static const lg_inputs good = {true, true, true, true, true};
static lg_config config(void) {
    lg_config c = {0};
    c.channels = 61u;
    c.max_run_ms = 120000u;
    c.ticket_lifetime_ms = 200u;
    c.max_poll_gap_ms = 20u;
    c.calibration_id = 17u;
    c.fixed_input_mw = 4000u;
    c.input_budget_mw = 20400u;
    for (unsigned i = 0; i < LG_MAX_CHANNELS; ++i) c.channel_input_mw[i] = 300u;
    return c;
}
static void put16(uint8_t *p, uint16_t value) {
    p[0] = (uint8_t)value; p[1] = (uint8_t)(value >> 8);
}
static void refresh_crc(uint8_t *packet, size_t length) {
    uint16_t crc = aiw_crc16_ccitt_false(packet, length - AIW_CRC_BYTES);
    put16(packet + length - AIW_CRC_BYTES, crc);
}
static void dark(const lg_guard *g) {
    assert(!g->output_enable);
    for (unsigned i = 0; i < LG_MAX_CHANNELS; ++i) assert(g->pwm[i] == 0u);
}
static void encoded_for_ticket(lg_guard *g, uint32_t now, uint32_t source_seq,
                               bool hand_present, bool include_ai_pwm,
                               uint8_t packet[AIW_MAX_PACKET_BYTES], size_t *length) {
    lg_frame ticket;
    assert(lg_issue_ticket(g, now, &ticket));
    aiw_result result = {0};
    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 = source_seq - 1u;
    result.source_frame_seq = source_seq;
    result.status_flags = hand_present ? AIW_RESULT_FLAG_HAND_PRESENT : 0u;
    if (hand_present && include_ai_pwm) result.pwm[0] = 128u;
    assert(aiw_encode_result(&result, packet, AIW_MAX_PACKET_BYTES, length));
}
int main(void) {
    lg_config cfg = config();
    lg_guard g;
    assert(lg_init(&g, &cfg, UINT64_C(0x12345678ABCDEF01), 0u));
    lg_poll(&g, 0u, good);
    assert(lg_arm(&g, LG_AI, 1000u, 0u));
    uint8_t packet[AIW_MAX_PACKET_BYTES] = {0};
    size_t length = 0u;

    encoded_for_ticket(&g, 0u, 1u, true, true, packet, &length);
    aiw_result decoded = {0};
    assert(aiw_decode_result(packet, length, &decoded));
    assert(lg_accept_ai_decoded_result(&g, &decoded, 0u));
    assert(g.output_enable && g.pwm[0] == 128u);
    decoded.channels = UINT16_MAX;
    assert(!lg_accept_ai_decoded_result(&g, &decoded, 1u));
    dark(&g);

    /* A fresh ticket carrying an old image sequence must turn the prior output off. */
    encoded_for_ticket(&g, 1u, 1u, true, true, packet, &length);
    assert(!lg_accept_ai_packet(&g, packet, length, 1u));
    dark(&g);

    /* A malformed packet consumes permission and cannot leave an old frame active. */
    encoded_for_ticket(&g, 2u, 2u, true, true, packet, &length);
    assert(lg_accept_ai_packet(&g, packet, length, 2u));
    packet[length - 1u] ^= 0x40u;
    assert(!lg_accept_ai_packet(&g, packet, length, 3u));
    dark(&g);

    encoded_for_ticket(&g, 4u, 3u, true, true, packet, &length);
    assert(lg_accept_ai_packet(&g, packet, length, 4u));
    /* A structurally valid but wrong calibration is rejected by policy, not CRC. */
    encoded_for_ticket(&g, 5u, 4u, true, true, packet, &length);
    packet[20] ^= 1u; /* Calibration id byte. */
    refresh_crc(packet, length);
    assert(!lg_accept_ai_packet(&g, packet, length, 5u));
    dark(&g);

    encoded_for_ticket(&g, 6u, 5u, true, true, packet, &length);
    packet[24] = 60u; packet[25] = 0u; /* Width no longer matches payload length. */
    refresh_crc(packet, length);
    assert(!lg_accept_ai_packet(&g, packet, length, 6u));
    dark(&g);

    encoded_for_ticket(&g, 7u, 6u, true, true, packet, &length);
    assert(lg_accept_ai_packet(&g, packet, length, 7u));
    assert(g.output_enable);
    encoded_for_ticket(&g, 8u, 7u, false, false, packet, &length);
    assert(lg_accept_ai_packet(&g, packet, length, 8u));
    assert(g.armed && g.mode == LG_AI && !g.hand_present);
    dark(&g);
    encoded_for_ticket(&g, 9u, 8u, true, true, packet, &length);
    assert(lg_accept_ai_packet(&g, packet, length, 9u));
    assert(g.output_enable && g.hand_present);

    /* Manual mode accepts fresh presence only, never AI PWM from the wire. */
    lg_stop(&g);
    assert(lg_arm(&g, LG_MANUAL, 500u, 10u));
    encoded_for_ticket(&g, 10u, 9u, true, true, packet, &length);
    assert(!lg_accept_ai_packet(&g, packet, length, 10u));
    assert(!g.hand_present);
    dark(&g);
    encoded_for_ticket(&g, 11u, 10u, true, false, packet, &length);
    assert(lg_accept_ai_packet(&g, packet, length, 11u));
    assert(g.hand_present && !g.output_enable);
    uint8_t manual_pwm[LG_MAX_CHANNELS] = {200u};
    assert(lg_set_manual(&g, manual_pwm, 61u, 11u));
    assert(g.output_enable && g.pwm[0] == 200u);
    puts("PASS: wire-to-guard path, session/ticket/calibration checks, image-sequence anti-replay and malformed-packet fail-dark.");
    return 0;
}
