#include "nail_led_aiw_adapter.h"

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

#define CHANNELS 3u
#define CALIBRATION_ID 0x41u
#define SESSION_ID UINT64_C(0xA110CA1B)

typedef struct {
    nlm_aiw_mapper_context mapper;
    nlm_profile profile;
    nlm_policy policy;
    nlm_aiw_model_frame model;
    aiw_soc_capture capture;
    uint8_t weights[CHANNELS][NLM_GRID_CELLS];
    uint8_t max_pwm[CHANNELS];
    uint8_t nail_probability[NLM_GRID_CELLS];
} harness;

static bool capture_fresh(void *context, const aiw_ticket *request,
                          aiw_soc_capture *capture)
{
    harness *h = context;
    assert(request != NULL && capture != NULL);
    *capture = h->capture;
    return true;
}

static bool infer_nails(void *context, const aiw_soc_capture *capture,
                        uint16_t channels, aiw_soc_inference *inference)
{
    harness *h = context;
    return nlm_aiw_mapper_infer(&h->mapper, capture, channels, inference);
}

static void init_harness(harness *h)
{
    memset(h, 0, sizeof(*h));
    h->weights[0][0] = 255u;
    h->weights[1][0] = 9u;
    h->weights[1][1] = 1u;
    h->weights[2][2] = 255u;
    h->max_pwm[0] = 120u;
    h->max_pwm[1] = 200u;
    h->max_pwm[2] = 255u;
    h->nail_probability[0] = 255u;
    h->profile = (nlm_profile){
        .optical_calibration_id = CALIBRATION_ID,
        .channels = CHANNELS,
        .calibrated = true,
        .irradiance_weights = h->weights,
        .max_pwm = h->max_pwm
    };
    h->policy = (nlm_policy){
        .optical_calibration_id = CALIBRATION_ID,
        .minimum_nail_energy_permille = 900u,
        .minimum_model_confidence = 200u,
        .requested_pwm = 180u
    };
    h->model = (nlm_aiw_model_frame){
        .magic = NLM_AIW_MODEL_FRAME_MAGIC,
        .version = NLM_AIW_MODEL_FRAME_VERSION,
        .optical_calibration_id = CALIBRATION_ID,
        .source_frame_seq = 12u,
        .model_valid = true,
        .hand_present = true,
        .model_confidence = 240u,
        .nail_probability = h->nail_probability
    };
    h->capture = (aiw_soc_capture){
        .frame_handle = &h->model,
        .capture_baseline_seq = 10u,
        .source_frame_seq = 12u,
        .exposure_started_after_request = true
    };
    assert(nlm_aiw_mapper_init(&h->mapper, &h->profile, &h->policy,
                              CALIBRATION_ID));
}

static size_t make_request(uint8_t *packet, uint32_t ticket_id,
                           uint64_t minimum_frame_seq)
{
    const aiw_ticket ticket = {
        .session_id = SESSION_ID,
        .ticket_id = ticket_id,
        .calibration_id = CALIBRATION_ID,
        .channels = CHANNELS,
        .min_source_frame_seq = minimum_frame_seq
    };
    size_t length = 0u;
    assert(aiw_encode_ticket(&ticket, packet,
                             AIW_HEADER_BYTES + AIW_TICKET_PAYLOAD_BYTES + AIW_CRC_BYTES,
                             &length));
    return length;
}

static void test_mapped_outputs_reach_aiw_result(void)
{
    harness h;
    aiw_soc_peer peer = {0};
    aiw_result result = {0};
    uint8_t request[AIW_HEADER_BYTES + AIW_TICKET_PAYLOAD_BYTES + AIW_CRC_BYTES];
    uint8_t response[AIW_MAX_PACKET_BYTES];
    size_t response_length = 0u;
    init_harness(&h);
    assert(aiw_soc_peer_init(&peer, SESSION_ID, CALIBRATION_ID, CHANNELS,
                             capture_fresh, infer_nails, &h));
    const size_t request_length = make_request(request, 1u, 11u);
    assert(aiw_soc_peer_process(&peer, request, request_length, response,
                                sizeof(response), &response_length) ==
           AIW_SOC_PEER_RESPONSE_ENCODED);
    assert(aiw_decode_result(response, response_length, &result));
    assert(result.status_flags == AIW_RESULT_FLAG_HAND_PRESENT);
    assert(result.pwm[0] == 120u); /* Per-channel optical PWM ceiling. */
    assert(result.pwm[1] == 180u); /* Exactly 900 permille nail overlap. */
    assert(result.pwm[2] == 0u);   /* No nail intersection. */
    assert(h.mapper.last_accepted_frame_seq == 12u);
}

static void test_stale_model_result_fails_dark(void)
{
    harness h;
    aiw_soc_peer peer = {0};
    aiw_result result = {0};
    uint8_t request[AIW_HEADER_BYTES + AIW_TICKET_PAYLOAD_BYTES + AIW_CRC_BYTES];
    uint8_t response[AIW_MAX_PACKET_BYTES];
    size_t response_length = 0u;
    init_harness(&h);
    h.model.source_frame_seq = 11u;
    assert(aiw_soc_peer_init(&peer, SESSION_ID, CALIBRATION_ID, CHANNELS,
                             capture_fresh, infer_nails, &h));
    const size_t request_length = make_request(request, 1u, 11u);
    assert(aiw_soc_peer_process(&peer, request, request_length, response,
                                sizeof(response), &response_length) ==
           AIW_SOC_PEER_RESPONSE_ENCODED);
    assert(aiw_decode_result(response, response_length, &result));
    assert(result.status_flags == 0u && peer.inference_failures == 1u);
    for (uint16_t i = 0u; i < CHANNELS; ++i) assert(result.pwm[i] == 0u);
}

static void test_no_hand_and_low_confidence_fail_dark(void)
{
    harness h;
    aiw_soc_peer peer = {0};
    aiw_result result = {0};
    uint8_t request[AIW_HEADER_BYTES + AIW_TICKET_PAYLOAD_BYTES + AIW_CRC_BYTES];
    uint8_t response[AIW_MAX_PACKET_BYTES];
    size_t response_length = 0u;
    init_harness(&h);
    h.model.hand_present = false;
    assert(aiw_soc_peer_init(&peer, SESSION_ID, CALIBRATION_ID, CHANNELS,
                             capture_fresh, infer_nails, &h));
    size_t request_length = make_request(request, 1u, 11u);
    assert(aiw_soc_peer_process(&peer, request, request_length, response,
                                sizeof(response), &response_length) ==
           AIW_SOC_PEER_RESPONSE_ENCODED);
    assert(aiw_decode_result(response, response_length, &result));
    assert(result.status_flags == 0u);
    for (uint16_t i = 0u; i < CHANNELS; ++i) assert(result.pwm[i] == 0u);

    h.model.hand_present = true;
    h.model.model_confidence = 10u;
    h.capture.source_frame_seq = 13u;
    h.capture.capture_baseline_seq = 12u;
    h.model.source_frame_seq = 13u;
    request_length = make_request(request, 2u, 13u);
    assert(aiw_soc_peer_process(&peer, request, request_length, response,
                                sizeof(response), &response_length) ==
           AIW_SOC_PEER_RESPONSE_ENCODED);
    assert(aiw_decode_result(response, response_length, &result));
    assert(result.status_flags == 0u && peer.inference_failures == 1u);
    for (uint16_t i = 0u; i < CHANNELS; ++i) assert(result.pwm[i] == 0u);
}

int main(void)
{
    test_mapped_outputs_reach_aiw_result();
    test_stale_model_result_fails_dark();
    test_no_hand_and_low_confidence_fail_dark();
    puts("PASS: AIW peer + calibrated nail-to-channel mapper integration");
    return 0;
}
