#include "is31fl3236a_lamp_backend.h"

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

enum { MOCK_MAX_TRANSACTIONS = 512u };

typedef struct {
    size_t transaction_count;
    size_t fail_on_transaction;
    uint8_t last_pwm[IS31FL3236A_MAX_GROUP_CHIPS][IS31FL3236A_CHANNELS];
    uint8_t last_control[IS31FL3236A_MAX_GROUP_CHIPS][IS31FL3236A_CHANNELS];
    bool sdb_low;
    bool fail_sdb_command;
    bool driver_supply_valid;
    bool permit_active;
    bool led_power_requested;
    bool led_power_good;
    uint32_t sdb_release_calls;
    uint32_t sdb_assert_calls;
    uint32_t force_off_calls;
} mock_board;

static bool mock_write(void *context, uint8_t address,
                       const uint8_t *bytes, size_t length) {
    mock_board *m = context;
    assert(m->sdb_low && m->driver_supply_valid);
    assert(bytes && length >= 2u && length <= IS31FL3236A_MAX_TRANSACTION_BYTES);
    ++m->transaction_count;
    if (address >= 0x3Cu && address <= 0x3Fu && length == 37u &&
        bytes[0] == 0x01u) {
        memcpy(m->last_pwm[address - 0x3Cu], &bytes[1],
               IS31FL3236A_CHANNELS);
    }
    if (address >= 0x3Cu && address <= 0x3Fu && length == 37u &&
        bytes[0] == 0x26u) {
        memcpy(m->last_control[address - 0x3Cu], &bytes[1],
               IS31FL3236A_CHANNELS);
    }
    return m->fail_on_transaction == 0u ||
           m->transaction_count != m->fail_on_transaction;
}

static bool mock_sdb_low(void *context) {
    return ((mock_board *)context)->sdb_low;
}

static bool mock_supply_valid(void *context) {
    return ((mock_board *)context)->driver_supply_valid;
}

static bool mock_permit(void *context) {
    return ((mock_board *)context)->permit_active;
}

static bool mock_set_sdb_low(void *context, bool asserted) {
    mock_board *m = context;
    if (m->fail_sdb_command) return false;
    if (asserted) ++m->sdb_assert_calls;
    else ++m->sdb_release_calls;
    m->sdb_low = asserted;
    return true;
}

static bool mock_force_off(void *context, bool asserted) {
    mock_board *m = context;
    ++m->force_off_calls;
    m->sdb_low = asserted;
    m->permit_active = false;
    m->led_power_requested = false;
    return asserted;
}

static bool mock_set_led_power(void *context, bool enabled) {
    ((mock_board *)context)->led_power_requested = enabled;
    return true;
}

static bool mock_led_power_good(void *context) {
    return ((mock_board *)context)->led_power_good;
}

static is31fl3236a_io make_driver_io(mock_board *m) {
    is31fl3236a_io io = {0};
    io.context = m;
    io.write_transaction = mock_write;
    io.sdb_is_low = mock_sdb_low;
    io.driver_supply_is_valid = mock_supply_valid;
    io.output_permit_is_active = mock_permit;
    io.set_sdb_low = mock_set_sdb_low;
    io.force_outputs_off = mock_force_off;
    return io;
}

static lg_config make_config(uint16_t channels) {
    lg_config c = {0};
    c.channels = channels;
    c.max_run_ms = 5000u;
    c.ticket_lifetime_ms = 200u;
    c.max_poll_gap_ms = 20u;
    c.calibration_id = 9u;
    c.fixed_input_mw = 1000u;
    c.input_budget_mw = 500000u;
    for (uint16_t i = 0u; i < LG_MAX_CHANNELS; ++i)
        c.channel_input_mw[i] = 10u;
    return c;
}

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

static void activate(lg_guard *guard, uint16_t channels, uint64_t session,
                     uint32_t now) {
    lg_config c = make_config(channels);
    lg_frame frame = {0};
    assert(lg_init(guard, &c, session, now));
    lg_poll(guard, now, good_inputs);
    assert(lg_arm(guard, LG_AI, 4000u, now));
    assert(lg_issue_ticket(guard, now, &frame));
    frame.source_frame_seq = 2u;
    frame.capture_baseline_seq = 1u;
    frame.hand_present = true;
    for (uint16_t i = 0u; i < channels; ++i)
        frame.pwm[i] = (uint8_t)(40u + (i % 100u));
    assert(lg_accept_ai(guard, &frame, now));
}

static void update_frame(lg_guard *guard, uint32_t now, uint8_t value) {
    lg_frame frame = {0};
    lg_poll(guard, now, good_inputs);
    assert(lg_issue_ticket(guard, now, &frame));
    frame.capture_baseline_seq = 2u;
    frame.source_frame_seq = 3u;
    frame.hand_present = true;
    frame.pwm[0] = value;
    assert(lg_accept_ai(guard, &frame, now));
}

static void make_disarmed(lg_guard *guard, uint16_t channels,
                          uint64_t session, uint32_t now) {
    lg_config c = make_config(channels);
    assert(lg_init(guard, &c, session, now));
    lg_poll(guard, now, good_inputs);
    assert(guard->mode == LG_OFF && !guard->armed);
}

static void test_61_and_122_channel_integration(void) {
    const uint16_t channel_cases[] = {61u, 122u};
    for (size_t test_case = 0u;
         test_case < sizeof(channel_cases) / sizeof(channel_cases[0]);
         ++test_case) {
        uint16_t channels = channel_cases[test_case];
        size_t driver_count = (channels + 35u) / 36u;
        uint8_t addresses[4] = {0x3Cu, 0x3Du, 0x3Eu, 0x3Fu};
        uint8_t profile[LG_MAX_CHANNELS];
        memset(profile, 0x01, sizeof(profile));
        mock_board m = {.sdb_low = true, .driver_supply_valid = true,
                        .permit_active = true};
        is31fl3236a_io driver_io = make_driver_io(&m);
        is31fl3236a_lamp_backend backend;
        assert(is31fl3236a_lamp_backend_init(
            &backend, &driver_io, addresses, driver_count, channels,
            profile, channels, IS31FL3236A_PWM_22KHZ,
            mock_set_led_power, mock_led_power_good, &m, 250u));
        assert(m.sdb_low && !m.led_power_requested &&
               m.transaction_count == 7u * driver_count);

        lg_guard guard;
        activate(&guard, channels, 0x100u + channels, 0u);
        assert(is31fl3236a_lamp_backend_sync(&backend, &guard, 0u) ==
               LG_OUTPUT_PENDING);
        assert(m.sdb_low && m.led_power_requested &&
               !m.led_power_good && m.sdb_release_calls == 0u);
        for (size_t chip = 0u; chip < driver_count; ++chip)
            assert(backend.drivers[chip].frame_loaded &&
                   !backend.drivers[chip].outputs_enabled);

        m.led_power_good = true;
        assert(is31fl3236a_lamp_backend_sync(&backend, &guard, 1u) ==
               LG_OUTPUT_ON);
        assert(!m.sdb_low && m.sdb_release_calls == 1u);
        for (uint16_t channel = 0u; channel < channels; ++channel) {
            size_t chip = channel / IS31FL3236A_CHANNELS;
            size_t output = channel % IS31FL3236A_CHANNELS;
            assert(m.last_pwm[chip][output] == guard.pwm[channel]);
            assert(m.last_control[chip][output] == 0x01u);
        }
        for (size_t channel = channels;
             channel < driver_count * IS31FL3236A_CHANNELS; ++channel) {
            size_t chip = channel / IS31FL3236A_CHANNELS;
            size_t output = channel % IS31FL3236A_CHANNELS;
            assert(m.last_pwm[chip][output] == 0u);
            assert(m.last_control[chip][output] == 0u);
        }

        update_frame(&guard, 2u, 211u);
        assert(is31fl3236a_lamp_backend_sync(&backend, &guard, 2u) ==
               LG_OUTPUT_ON);
        assert(!m.sdb_low && m.sdb_release_calls == 2u);
        assert(m.last_pwm[0][0] == 211u);

        lg_poll(&guard, 3u, good_inputs);
        lg_frame hand_absent = {0};
        assert(lg_issue_ticket(&guard, 3u, &hand_absent));
        hand_absent.capture_baseline_seq = 3u;
        hand_absent.source_frame_seq = 4u;
        hand_absent.hand_present = false;
        assert(lg_accept_ai(&guard, &hand_absent, 3u));
        assert(is31fl3236a_lamp_backend_sync(&backend, &guard, 3u) ==
               LG_OUTPUT_OFF);
        assert(m.sdb_low && !m.led_power_requested &&
               !backend.output.fault_latched);
    }
}

static void test_bus_fault_latches_group_and_new_session_recovers(void) {
    uint8_t addresses[2] = {0x3Cu, 0x3Du};
    uint8_t profile[61];
    memset(profile, 0x01, sizeof(profile));
    mock_board m = {.sdb_low = true, .driver_supply_valid = true,
                    .permit_active = true, .led_power_good = true};
    is31fl3236a_io driver_io = make_driver_io(&m);
    is31fl3236a_lamp_backend backend;
    assert(is31fl3236a_lamp_backend_init(
        &backend, &driver_io, addresses, 2u, 61u, profile, sizeof(profile),
        IS31FL3236A_PWM_22KHZ, mock_set_led_power, mock_led_power_good,
        &m, 250u));
    lg_guard active;
    activate(&active, 61u, 0xA001u, 0u);
    assert(is31fl3236a_lamp_backend_sync(&backend, &active, 0u) ==
           LG_OUTPUT_ON);

    update_frame(&active, 1u, 220u);
    size_t before_update = m.transaction_count;
    /* Four shutdown writes, three first-chip frame writes, then fail on chip 2. */
    m.fail_on_transaction = before_update + 8u;
    assert(is31fl3236a_lamp_backend_sync(&backend, &active, 1u) ==
           LG_OUTPUT_LATCHED_FAULT);
    assert(m.sdb_low && !m.led_power_requested &&
           backend.output.fault_latched && m.force_off_calls > 0u);
    assert(backend.drivers[0].fault_latched &&
           backend.drivers[1].fault_latched);

    m.fail_on_transaction = 0u;
    lg_guard recovery;
    make_disarmed(&recovery, 61u, 0xA002u, 2u);
    uint32_t force_calls_before = m.force_off_calls;
    m.fail_sdb_command = true;
    assert(!is31fl3236a_lamp_backend_recover(&backend, &recovery));
    assert(m.force_off_calls > force_calls_before && m.sdb_low &&
           !m.led_power_requested && backend.output.fault_latched);
    m.fail_sdb_command = false;
    make_disarmed(&recovery, 61u, 0xA003u, 3u);
    assert(is31fl3236a_lamp_backend_recover(&backend, &recovery));
    assert(m.sdb_low && !m.led_power_requested &&
           !backend.output.fault_latched &&
           backend.drivers[0].initialized && backend.drivers[1].initialized &&
           !backend.drivers[0].fault_latched &&
           !backend.drivers[1].fault_latched);

    m.permit_active = true;
    activate(&recovery, 61u, 0xA003u, 4u);
    assert(is31fl3236a_lamp_backend_sync(&backend, &recovery, 4u) ==
           LG_OUTPUT_ON);
    assert(!m.sdb_low && m.led_power_requested);
}

static void test_invalid_profile_and_channel_configuration_rejected(void) {
    uint8_t addresses[4] = {0x3Cu, 0x3Du, 0x3Eu, 0x3Fu};
    uint8_t profile[122];
    memset(profile, 0x01, sizeof(profile));
    mock_board m = {.sdb_low = true, .driver_supply_valid = true,
                    .permit_active = true};
    is31fl3236a_io driver_io = make_driver_io(&m);
    is31fl3236a_lamp_backend backend;
    profile[10] = 0x08u;
    assert(!is31fl3236a_lamp_backend_init(
        &backend, &driver_io, addresses, 4u, 122u, profile, sizeof(profile),
        IS31FL3236A_PWM_22KHZ, mock_set_led_power, mock_led_power_good,
        &m, 250u));
    assert(m.transaction_count == 0u && m.sdb_low);
    profile[10] = 0x01u;
    assert(!is31fl3236a_lamp_backend_init(
        &backend, &driver_io, addresses, 2u, 122u, profile, sizeof(profile),
        IS31FL3236A_PWM_22KHZ, mock_set_led_power, mock_led_power_good,
        &m, 250u));
    assert(m.transaction_count == 0u && m.sdb_low);
    addresses[1] = addresses[0];
    assert(!is31fl3236a_lamp_backend_init(
        &backend, &driver_io, addresses,
        2u, 61u, profile, 61u, IS31FL3236A_PWM_22KHZ,
        mock_set_led_power, mock_led_power_good, &m, 250u));
    assert(m.transaction_count == 0u && m.sdb_low);
}

int main(void) {
    test_61_and_122_channel_integration();
    test_bus_fault_latches_group_and_new_session_recovers();
    test_invalid_profile_and_channel_configuration_rejected();
    puts("PASS: IS31FL3236A backend integrates with guard output/power-good sequencing for 61/122 channels and recovers only in a new session.");
    return 0;
}
