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

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 aiw_result make_result(uint16_t channels) {
    aiw_result r = {0};
    r.session_id = UINT64_C(0x12345678ABCDEF01);
    r.ticket_id = 0x10203040u;
    r.calibration_id = 0x55667788u;
    r.channels = channels;
    r.source_frame_seq = UINT64_C(0x1234567890ABCDEF);
    r.capture_baseline_seq = r.source_frame_seq - 1u;
    r.status_flags = AIW_RESULT_FLAG_HAND_PRESENT;
    for (uint16_t i = 0; i < channels; ++i) r.pwm[i] = (uint8_t)((i * 37u) & 0xFFu);
    return r;
}

static void round_trip(uint16_t channels) {
    aiw_result input = make_result(channels), output = {0};
    uint8_t packet[AIW_MAX_PACKET_BYTES] = {0};
    size_t written = 0u;
    assert(aiw_encode_result(&input, packet, sizeof(packet), &written));
    assert(written == AIW_HEADER_BYTES + AIW_RESULT_FIXED_PAYLOAD_BYTES + channels + AIW_CRC_BYTES);
    assert(aiw_decode_result(packet, written, &output));
    assert(output.session_id == input.session_id);
    assert(output.ticket_id == input.ticket_id);
    assert(output.calibration_id == input.calibration_id);
    assert(output.source_frame_seq == input.source_frame_seq);
    assert(output.capture_baseline_seq == input.capture_baseline_seq);
    assert(output.channels == channels);
    assert(output.status_flags == input.status_flags);
    assert(memcmp(output.pwm, input.pwm, channels) == 0);
    for (uint16_t i = channels; i < AIW_MAX_CHANNELS; ++i) assert(output.pwm[i] == 0u);

    aiw_ticket ticket_in = {input.session_id, input.ticket_id,
                            input.calibration_id, input.channels, input.source_frame_seq};
    aiw_ticket ticket_out = {0};
    assert(aiw_encode_ticket(&ticket_in, packet, sizeof(packet), &written));
    assert(written == AIW_HEADER_BYTES + AIW_TICKET_PAYLOAD_BYTES + AIW_CRC_BYTES);
    assert(aiw_decode_ticket(packet, written, &ticket_out));
    assert(ticket_out.session_id == ticket_in.session_id);
    assert(ticket_out.ticket_id == ticket_in.ticket_id);
    assert(ticket_out.calibration_id == ticket_in.calibration_id);
    assert(ticket_out.channels == ticket_in.channels);
    assert(ticket_out.min_source_frame_seq == ticket_in.min_source_frame_seq);
}

int main(void) {
    assert(aiw_crc16_ccitt_false((const uint8_t *)"123456789", 9u) == 0x29B1u);
    round_trip(61u);
    round_trip(122u);

    aiw_result input = make_result(61u), output = {0};
    uint8_t packet[AIW_MAX_PACKET_BYTES] = {0};
    size_t length = 0u;
    assert(aiw_encode_result(&input, packet, sizeof(packet), &length));

    output.session_id = 77u;
    assert(!aiw_decode_result(packet, length - 1u, &output));
    assert(output.session_id == 0u); /* Failed decode cannot leave stale output. */
    assert(!aiw_decode_result(packet, length + 1u, &output));

    uint8_t bad[AIW_MAX_PACKET_BYTES];
    memcpy(bad, packet, length); bad[0] ^= 0x01u;
    assert(!aiw_decode_result(bad, length, &output));
    memcpy(bad, packet, length); bad[4] = 1u;
    assert(!aiw_decode_result(bad, length, &output));
    memcpy(bad, packet, length); bad[5] = 0x32u;
    assert(!aiw_decode_result(bad, length, &output));
    memcpy(bad, packet, length); bad[8] ^= 0x80u;
    assert(!aiw_decode_result(bad, length, &output));

    memcpy(bad, packet, length);
    put16(bad + 6, 1u); refresh_crc(bad, length);
    assert(!aiw_decode_result(bad, length, &output));
    memcpy(bad, packet, length);
    put16(bad + 24, 0u); refresh_crc(bad, length);
    assert(!aiw_decode_result(bad, length, &output));
    memcpy(bad, packet, length);
    put16(bad + 24, AIW_MAX_CHANNELS + 1u); refresh_crc(bad, length);
    assert(!aiw_decode_result(bad, length, &output));
    memcpy(bad, packet, length);
    put16(bad + 24, 60u); refresh_crc(bad, length); /* Header count and payload disagree. */
    assert(!aiw_decode_result(bad, length, &output));

    input.session_id = 0u;
    assert(!aiw_encode_result(&input, packet, sizeof(packet), &length));
    input = make_result(61u);
    input.capture_baseline_seq = input.source_frame_seq;
    assert(!aiw_encode_result(&input, packet, sizeof(packet), &length));
    input = make_result(122u);
    assert(!aiw_encode_result(&input, packet, AIW_MAX_PACKET_BYTES - 1u, &length));
    assert(length == 0u);

    input = make_result(61u);
    input.status_flags = 0u;
    memset(input.pwm, 0, input.channels);
    assert(aiw_encode_result(&input, packet, sizeof(packet), &length));
    assert(aiw_decode_result(packet, length, &output));
    assert(output.status_flags == 0u);
    packet[AIW_HEADER_BYTES + AIW_RESULT_FIXED_PAYLOAD_BYTES] = 1u;
    refresh_crc(packet, length);
    assert(!aiw_decode_result(packet, length, &output));
    input.pwm[0] = 1u;
    assert(!aiw_encode_result(&input, packet, sizeof(packet), &length));
    input = make_result(61u);
    input.status_flags = AIW_RESULT_ALLOWED_FLAGS | 0x80u;
    assert(!aiw_encode_result(&input, packet, sizeof(packet), &length));
    assert(!aiw_crc16_ccitt_false(NULL, 1u));

    aiw_ui_session_open open_in = {UINT64_C(0xFEDCBA9876543210)};
    aiw_ui_session_open open_out = {0};
    assert(aiw_encode_ui_session_open(&open_in, packet, sizeof(packet), &length));
    assert(length == AIW_HEADER_BYTES + 8u + AIW_CRC_BYTES);
    assert(aiw_decode_ui_session_open(packet, length, &open_out));
    assert(open_out.session_id == open_in.session_id);
    aiw_message decoded_message;
    assert(aiw_decode_message(packet, length, &decoded_message));
    assert(decoded_message.type == AIW_MESSAGE_UI_SESSION_OPEN);
    assert(decoded_message.value.ui_session_open.session_id == open_in.session_id);

    aiw_ui_command command_in = {open_in.session_id, 0xA1B2C3D4u, 7u};
    aiw_ui_command command_out = {0};
    assert(aiw_encode_ui_command(&command_in, packet, sizeof(packet), &length));
    assert(aiw_decode_ui_command(packet, length, &command_out));
    assert(command_out.session_id == command_in.session_id);
    assert(command_out.sequence == command_in.sequence);
    assert(command_out.button == command_in.button);
    assert(aiw_decode_message(packet, length, &decoded_message));
    assert(decoded_message.type == AIW_MESSAGE_UI_COMMAND);

    aiw_ui_ack ack_in = {open_in.session_id, command_in.sequence,
                         AIW_UI_ACK_ACCEPTED, 2u,
                         AIW_UI_ACK_FLAG_RUNNING, 59321u};
    aiw_ui_ack ack_out = {0};
    assert(aiw_encode_ui_ack(&ack_in, packet, sizeof(packet), &length));
    assert(aiw_decode_ui_ack(packet, length, &ack_out));
    assert(ack_out.session_id == ack_in.session_id &&
           ack_out.sequence == ack_in.sequence && ack_out.status == ack_in.status &&
           ack_out.selected_mode == ack_in.selected_mode &&
           ack_out.flags == ack_in.flags && ack_out.remaining_ms == ack_in.remaining_ms);
    assert(aiw_decode_message(packet, length, &decoded_message));
    assert(decoded_message.type == AIW_MESSAGE_UI_ACK);

    packet[AIW_HEADER_BYTES + 12u] = 0xFFu;
    refresh_crc(packet, length);
    assert(!aiw_decode_ui_ack(packet, length, &ack_out));

    ack_in.sequence = 0u;
    ack_in.status = AIW_UI_ACK_SESSION_OPENED;
    ack_in.selected_mode = 0u;
    ack_in.flags = 0u;
    ack_in.remaining_ms = 0u;
    assert(aiw_encode_ui_ack(&ack_in, packet, sizeof(packet), &length));
    assert(aiw_decode_ui_ack(packet, length, &ack_out));
    ack_in.status = AIW_UI_ACK_ACCEPTED;
    assert(!aiw_encode_ui_ack(&ack_in, packet, sizeof(packet), &length));
    ack_in.sequence = 1u;
    ack_in.status = (aiw_ui_ack_status)0xFFu;
    assert(!aiw_encode_ui_ack(&ack_in, packet, sizeof(packet), &length));
    command_in.sequence = 0u;
    assert(!aiw_encode_ui_command(&command_in, packet, sizeof(packet), &length));

    puts("PASS: AI result/ticket and UI session/command/ACK wire round-trips, endian/CRC check, strict metadata validation.");
    return 0;
}
