#include "blue_force.h"
#include <cstring>

#ifdef ARDUINO
#include <Arduino.h>
#else
// Host-side stub so this module (previously buildable ONLY on-device) can
// be exercised by a native Unity test -- same pattern used by network_id.cpp
// / threat_correlator.cpp. No millis() dependency here, just Serial.printf.
#include <cstdio>
#include <cstdarg>
struct NativeSerialStub {
    void printf(const char* fmt, ...) {
        va_list args;
        va_start(args, fmt);
        vprintf(fmt, args);
        va_end(args);
    }
};
static NativeSerialStub Serial;
#endif

static const char* TAG = "BlueForce";

// Whitelist entry
struct BlueForceEntry {
    char    serial[BLUE_FORCE_SERIAL_LEN + 1];  // null-terminated
    uint8_t hash[8];                             // pre-computed signature hash
    bool    active;
};

static BlueForceEntry s_whitelist[BLUE_FORCE_MAX_ENTRIES];
static uint8_t s_count = 0;

// ---------------------------------------------------------------------------
// FNV-1a 64-bit hash, truncated to 8 bytes (same as signature_hash in packets)
// ---------------------------------------------------------------------------
static uint64_t fnv1a_64(const uint8_t* data, size_t len) {
    uint64_t hash = 0xcbf29ce484222325ULL;  // FNV offset basis
    for (size_t i = 0; i < len; i++) {
        hash ^= data[i];
        hash *= 0x100000001b3ULL;           // FNV prime
    }
    return hash;
}

void blue_force_compute_hash(const char* serial, uint8_t* hash_out) {
    size_t len = strlen(serial);
    uint64_t h = fnv1a_64((const uint8_t*)serial, len);

    // Store as big-endian 8 bytes
    hash_out[0] = (h >> 56) & 0xFF;
    hash_out[1] = (h >> 48) & 0xFF;
    hash_out[2] = (h >> 40) & 0xFF;
    hash_out[3] = (h >> 32) & 0xFF;
    hash_out[4] = (h >> 24) & 0xFF;
    hash_out[5] = (h >> 16) & 0xFF;
    hash_out[6] = (h >> 8)  & 0xFF;
    hash_out[7] = (h)       & 0xFF;
}

// ---------------------------------------------------------------------------
void blue_force_init() {
    for (int i = 0; i < BLUE_FORCE_MAX_ENTRIES; i++) {
        s_whitelist[i].active = false;
    }
    s_count = 0;
    Serial.printf("[%s] Initialized. Max entries: %d\n", TAG, BLUE_FORCE_MAX_ENTRIES);
}

// ---------------------------------------------------------------------------
bool blue_force_add(const char* serial) {
    if (!serial || strlen(serial) == 0) return false;

    // Check if already in list
    if (blue_force_check_serial(serial)) {
        Serial.printf("[%s] Serial already whitelisted: %.20s\n", TAG, serial);
        return true;
    }

    // Find empty slot
    for (int i = 0; i < BLUE_FORCE_MAX_ENTRIES; i++) {
        if (!s_whitelist[i].active) {
            strncpy(s_whitelist[i].serial, serial, BLUE_FORCE_SERIAL_LEN);
            s_whitelist[i].serial[BLUE_FORCE_SERIAL_LEN] = '\0';
            // BUG FIX: hash the STORED (possibly truncated) serial, not the
            // raw input. Real ANSI/CTA-2063-A serials are always exactly
            // BLUE_FORCE_SERIAL_LEN (20) chars, so this never changes
            // behavior for conforming input -- but hashing the untruncated
            // raw string meant an out-of-spec >20-char input would produce
            // a hash that didn't match what blue_force_check_serial()'s
            // truncated strncmp would consider the same entry, silently
            // desyncing the two lookup paths for malformed input.
            blue_force_compute_hash(s_whitelist[i].serial, s_whitelist[i].hash);
            s_whitelist[i].active = true;
            s_count++;
            Serial.printf("[%s] Added: %.20s (count=%d)\n", TAG, serial, s_count);
            return true;
        }
    }

    Serial.printf("[%s] Whitelist full (%d entries)\n", TAG, BLUE_FORCE_MAX_ENTRIES);
    return false;
}

// ---------------------------------------------------------------------------
bool blue_force_remove(const char* serial) {
    if (!serial) return false;

    for (int i = 0; i < BLUE_FORCE_MAX_ENTRIES; i++) {
        if (s_whitelist[i].active &&
            strncmp(s_whitelist[i].serial, serial, BLUE_FORCE_SERIAL_LEN) == 0) {
            s_whitelist[i].active = false;
            s_count--;
            Serial.printf("[%s] Removed: %.20s (count=%d)\n", TAG, serial, s_count);
            return true;
        }
    }
    return false;
}

// ---------------------------------------------------------------------------
bool blue_force_check_hash(const uint8_t* signature_hash) {
    for (int i = 0; i < BLUE_FORCE_MAX_ENTRIES; i++) {
        if (s_whitelist[i].active &&
            memcmp(s_whitelist[i].hash, signature_hash, 8) == 0) {
            return true;
        }
    }
    return false;
}

// ---------------------------------------------------------------------------
bool blue_force_check_serial(const char* serial) {
    if (!serial) return false;
    for (int i = 0; i < BLUE_FORCE_MAX_ENTRIES; i++) {
        if (s_whitelist[i].active &&
            strncmp(s_whitelist[i].serial, serial, BLUE_FORCE_SERIAL_LEN) == 0) {
            return true;
        }
    }
    return false;
}

// ---------------------------------------------------------------------------
uint8_t blue_force_count() {
    return s_count;
}

// ---------------------------------------------------------------------------
void blue_force_clear() {
    for (int i = 0; i < BLUE_FORCE_MAX_ENTRIES; i++) {
        s_whitelist[i].active = false;
    }
    s_count = 0;
    Serial.printf("[%s] Whitelist cleared\n", TAG);
}
