#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <stdbool.h>

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>

#include <esp_log.h>

#include <wiegand.h>
#include "auth.h"

#define CONFIG_EXAMPLE_BUF_SIZE 50
#define IDTAG_MAX_LEN 20

static const char *TAG = "WiegandReader";

// ---- Parâmetro global/local para controlar a convenção de paridade ----
static const bool PARITY_INVERTED = false; // mude para true se o seu leitor vier "invertido"

static wiegand_reader_t reader;
static QueueHandle_t queue = NULL;

typedef struct
{
    uint8_t data[CONFIG_EXAMPLE_BUF_SIZE];
    size_t bits;
    size_t bytes;
} data_packet_t;

static inline uint8_t get_bit_msb_first(const uint8_t *buf, size_t bit_index)
{
    return (buf[bit_index / 8] >> (7 - (bit_index % 8))) & 0x01;
}

// Versão parametrizável de verificação de paridade
static bool check_parity_param(const uint8_t *buf, size_t bits, bool invert)
{
    if (bits != 26 && bits != 34)
        return false;

    const int first_parity = get_bit_msb_first(buf, 0);
    const int last_parity = get_bit_msb_first(buf, bits - 1);

    uint32_t left = 0, right = 0;

    if (bits == 26)
    {
        for (int i = 1; i <= 12; i++)
            if (get_bit_msb_first(buf, i))
                left++;
        for (int i = 13; i <= 24; i++)
            if (get_bit_msb_first(buf, i))
                right++;
    }
    else // 34 bits
    {
        for (int i = 1; i <= 16; i++)
            if (get_bit_msb_first(buf, i))
                left++;
        for (int i = 17; i <= 32; i++)
            if (get_bit_msb_first(buf, i))
                right++;
    }

    // Regra padrão (W26/W34 “normais”): primeiro = PAR (left), último = ÍMPAR (right)
    bool exp_first = (left % 2 == 0);
    bool exp_last = (right % 2 == 1);

    // Se invertido, troca as expectativas (par <-> ímpar)
    if (invert)
    {
        exp_first = !exp_first;
        exp_last = !exp_last;
    }

    return (first_parity == exp_first) && (last_parity == exp_last);
}

// CRC-8/ATM (polinómio 0x07, init 0x00, sem ref, xor 0x00)
static uint8_t crc8_atm(const uint8_t *data, size_t len)
{
    uint8_t crc = 0x00;
    for (size_t i = 0; i < len; i++)
    {
        crc ^= data[i];
        for (int b = 0; b < 8; b++)
        {
            crc = (crc & 0x80) ? (uint8_t)((crc << 1) ^ 0x07) : (uint8_t)(crc << 1);
        }
    }
    return crc;
}

// CRC-16/IBM (CRC-16-ANSI) poly 0xA001 (refin/refout), init 0xFFFF
static uint16_t crc16_ibm(const uint8_t *data, size_t len)
{
    uint16_t crc = 0xFFFF;
    for (size_t i = 0; i < len; i++)
    {
        crc ^= data[i];
        for (int b = 0; b < 8; b++)
        {
            if (crc & 1)
                crc = (crc >> 1) ^ 0xA001;
            else
                crc >>= 1;
        }
    }
    return crc;
}

// Extrai payload (sem paridades) e devolve FC/CN para W26/34
static bool wiegand_extract_fc_cn(const uint8_t *buf, size_t bits, uint32_t *fc, uint32_t *cn)
{
    if (bits != 26 && bits != 34)
        return false;

    uint32_t payload = 0;
    size_t payload_bits = bits - 2;

    for (size_t i = 0; i < payload_bits; i++)
    {
        size_t bit = 1 + i; // ignora bit de paridade inicial
        uint8_t bitval = (buf[bit / 8] >> (7 - (bit % 8))) & 0x01;
        payload = (payload << 1) | bitval;
    }

    if (bits == 26)
    {
        *fc = (payload >> 16) & 0xFF; // 8b
        *cn = payload & 0xFFFF;       // 16b
    }
    else // 34 bits
    {
        *fc = (payload >> 16) & 0xFFFF; // 16b
        *cn = payload & 0xFFFF;         // 16b
    }

    return true;
}

static bool build_idtag_w26_4B(uint32_t fc, uint32_t cn, char *out, size_t outlen)
{
    uint8_t raw[4] = {
        (uint8_t)(fc & 0xFF),
        (uint8_t)((cn >> 8) & 0xFF),
        (uint8_t)(cn & 0xFF),
        0};

    raw[3] = crc8_atm(raw, 3);

    if (outlen < 9)
        return false;

    int n = snprintf(out, outlen, "%02X%02X%02X%02X",
                     raw[0], raw[1], raw[2], raw[3]);
    return (n > 0 && (size_t)n < outlen);
}

static bool build_idtag_w34_7B(uint32_t fc, uint32_t cn, char *out, size_t outlen)
{
    uint8_t raw[7];
    raw[0] = 0x34;
    raw[1] = (uint8_t)((fc >> 8) & 0xFF);
    raw[2] = (uint8_t)(fc & 0xFF);
    raw[3] = (uint8_t)((cn >> 8) & 0xFF);
    raw[4] = (uint8_t)(cn & 0xFF);

    uint16_t crc = crc16_ibm(raw, 5);
    raw[5] = (uint8_t)((crc >> 8) & 0xFF);
    raw[6] = (uint8_t)(crc & 0xFF);

    if (outlen < 15)
        return false;

    int n = snprintf(out, outlen, "%02X%02X%02X%02X%02X%02X%02X",
                     raw[0], raw[1], raw[2], raw[3],
                     raw[4], raw[5], raw[6]);
    return (n > 0 && (size_t)n < outlen);
}

// Callback chamado pelo esp_timer_task (contexto de task, NÃO ISR)
static void reader_callback(wiegand_reader_t *r)
{
    if (!queue)
    {
        ESP_LOGW(TAG, "Queue not ready, dropping packet");
        return;
    }

    data_packet_t p = {0};
    p.bits = r->bits;
    p.bytes = (r->bits + 7) / 8;

    if (p.bytes > sizeof(p.data))
        p.bytes = sizeof(p.data);

    memcpy(p.data, r->buf, p.bytes);

    if (xQueueSendToBack(queue, &p, 0) != pdPASS)
    {
        ESP_LOGW(TAG, "Queue full, dropping Wiegand packet");
    }
}

static void wiegand_task(void *arg)
{
    queue = xQueueCreate(8, sizeof(data_packet_t));
    if (!queue)
    {
        ESP_LOGE(TAG, "Failed to create queue");
        vTaskDelete(NULL);
        return;
    }

    ESP_ERROR_CHECK(wiegand_reader_init(&reader,
                                        21, 22, // GPIO D0, D1
                                        true,   // internal pullups
                                        CONFIG_EXAMPLE_BUF_SIZE,
                                        reader_callback,
                                        WIEGAND_MSB_FIRST,
                                        WIEGAND_LSB_FIRST));

    data_packet_t p;

    for (;;)
    {
        ESP_LOGI(TAG, "Waiting for Wiegand data...");

        if (xQueueReceive(queue, &p, portMAX_DELAY) != pdPASS)
            continue;

        ESP_LOGI(TAG, "Bits received: %d", (int)p.bits);

        if (!check_parity_param(p.data, p.bits, PARITY_INVERTED))
        {
            ESP_LOGW(TAG, "Parity check failed (%d bits). Dump:", (int)p.bits);
            ESP_LOG_BUFFER_HEX(TAG, p.data, p.bytes);
            continue;
        }

        char tag[IDTAG_MAX_LEN + 1] = {0}; // OCPP 1.6: máx 20 chars (+NUL)
        uint32_t fc = 0, cn = 0;

        if (!wiegand_extract_fc_cn(p.data, p.bits, &fc, &cn))
        {
            ESP_LOGW(TAG, "Unsupported bit length: %d", (int)p.bits);
            continue;
        }

        bool ok = false;
        if (p.bits == 26)
        {
            ok = build_idtag_w26_4B(fc, cn, tag, sizeof(tag)); // 8 hex
        }
        else // 34
        {
            ok = build_idtag_w34_7B(fc, cn, tag, sizeof(tag)); // 14 hex
        }

        if (!ok)
        {
            ESP_LOGW(TAG, "Failed to build idTag");
            continue;
        }

        ESP_LOGI(TAG, "idTag: %s", tag); // apenas [0-9A-F], alfanumérico
        auth_process_tag(tag);
    }
}

static void wiegand_sim_task(void *arg)
{
    // lista fixa de idTags simuladas
    static const char *idtaglist[] = {
        "127239144069"};
    const size_t list_size = sizeof(idtaglist) / sizeof(idtaglist[0]);

    for (;;)
    {
        for (size_t i = 0; i < list_size; i++)
        {
            ESP_LOGI(TAG, "Simulação -> idTag: %s", idtaglist[i]);
            auth_process_tag(idtaglist[i]);

            // espera 30 segundos entre cada tag
            vTaskDelay(pdMS_TO_TICKS(30000));
        }
    }
}

void initWiegand(void)
{
    ESP_LOGI(TAG, "Initializing Wiegand reader");
    xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL);

    // Para testes, podes ativar o simulador:
    //ESP_LOGI(TAG, "Inicializando Wiegand simulado");
    //xTaskCreate(wiegand_sim_task, "WiegandSim",
    //            configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL);
}
