#include "hil_trace.h"

#if ENABLE_HIL_TRACE

#include <stdarg.h>
#ifdef ARDUINO_ARCH_ESP32
#include <esp_heap_caps.h>
#endif

static bool g_faults[static_cast<uint8_t>(HilFault::Count)] = {};
static uint32_t g_delay_ack_ms = 1000;
static uint32_t g_last_health_ms = 0;
static String g_command;

static const char *role_name(DeviceRole role) {
  return role == DeviceRole::Sender ? "sender" : "receiver";
}

void hil_trace_event(const char *event, const char *fields_json) {
  if (!event || !event[0]) {
    return;
  }
  char line[448] = {};
  int written = 0;
  if (fields_json && fields_json[0]) {
    written = snprintf(line, sizeof(line), "HIL:{\"event\":\"%s\",\"ms\":%lu,%s}\n",
                       event, static_cast<unsigned long>(millis()), fields_json);
  } else {
    written = snprintf(line, sizeof(line), "HIL:{\"event\":\"%s\",\"ms\":%lu}\n",
                       event, static_cast<unsigned long>(millis()));
  }
  if (written <= 0) return;
  size_t length = static_cast<size_t>(written);
  if (length >= sizeof(line)) length = sizeof(line) - 1;
  Serial.write(reinterpret_cast<const uint8_t *>(line), length);
}

void hil_trace_eventf(const char *event, const char *fields_fmt, ...) {
  char fields[320] = {};
  if (fields_fmt && fields_fmt[0]) {
    va_list args;
    va_start(args, fields_fmt);
    vsnprintf(fields, sizeof(fields), fields_fmt, args);
    va_end(args);
  }
  hil_trace_event(event, fields);
}

void hil_trace_init(DeviceRole role, uint16_t short_id, int reset_reason) {
  hil_trace_eventf("boot", "\"reset_reason\":%d,\"short_id\":%u", reset_reason, short_id);
  hil_trace_eventf("role", "\"role\":\"%s\",\"short_id\":%u", role_name(role), short_id);
}

static bool arm_fault(const String &name) {
  struct Mapping { const char *name; HilFault fault; };
  static const Mapping mappings[] = {
    {"drop_chunk", HilFault::DropChunk},
    {"duplicate_chunk", HilFault::DuplicateChunk},
    {"corrupt_chunk", HilFault::CorruptChunk},
    {"suppress_ack", HilFault::SuppressAck},
    {"delay_ack", HilFault::DelayAck},
    {"wrong_ack_batch", HilFault::WrongAckBatch},
  };
  for (const Mapping &mapping : mappings) {
    if (name == mapping.name) {
      g_faults[static_cast<uint8_t>(mapping.fault)] = true;
      hil_trace_eventf("fault_armed", "\"fault\":\"%s\"", mapping.name);
      return true;
    }
  }
  return false;
}

static void handle_command(const String &command) {
  if (command == "clear") {
    memset(g_faults, 0, sizeof(g_faults));
    hil_trace_event("faults_cleared");
    return;
  }
  if (command.startsWith("delay_ack:")) {
    long requested = command.substring(10).toInt();
    if (requested >= 0 && requested <= 10000) {
      g_delay_ack_ms = static_cast<uint32_t>(requested);
      arm_fault("delay_ack");
      return;
    }
  }
  if (!arm_fault(command)) {
    hil_trace_eventf("command_rejected", "\"reason\":\"unknown\",\"length\":%u",
                     static_cast<unsigned>(command.length()));
  }
}

void hil_trace_poll_commands() {
  // Commands are intentionally tiny and line-based to avoid dynamic JSON parsing
  // or meaningful loop latency. Accepted form: HILCMD:<one-shot-fault>\n.
  uint8_t budget = 64;
  while (Serial.available() && budget-- > 0) {
    char c = static_cast<char>(Serial.read());
    if (c == '\r') {
      continue;
    }
    if (c == '\n') {
      if (g_command.startsWith("HILCMD:")) {
        handle_command(g_command.substring(7));
      }
      g_command = "";
    } else if (g_command.length() < 63) {
      g_command += c;
    } else {
      g_command = "";
    }
  }
}

bool hil_fault_take(HilFault fault) {
  uint8_t index = static_cast<uint8_t>(fault);
  if (index >= static_cast<uint8_t>(HilFault::Count) || !g_faults[index]) {
    return false;
  }
  g_faults[index] = false;
  return true;
}

uint32_t hil_fault_delay_ms() {
  return g_delay_ack_ms;
}

uint32_t hil_crc32(const uint8_t *data, size_t len) {
  uint32_t crc = 0xFFFFFFFFUL;
  for (size_t i = 0; i < len; ++i) {
    crc ^= data[i];
    for (uint8_t bit = 0; bit < 8; ++bit) {
      crc = (crc >> 1) ^ ((crc & 1U) ? 0xEDB88320UL : 0);
    }
  }
  return ~crc;
}

void hil_trace_health(uint8_t queue_depth, uint8_t build_depth, uint8_t retry_count) {
  uint32_t now = millis();
  if (now - g_last_health_ms < 5000) {
    return;
  }
  g_last_health_ms = now;
#ifdef ARDUINO_ARCH_ESP32
  hil_trace_eventf("health", "\"heap_free\":%u,\"heap_min\":%u,\"stack_words\":%u,\"queue\":%u,\"build\":%u,\"retries\":%u",
                   ESP.getFreeHeap(), ESP.getMinFreeHeap(), uxTaskGetStackHighWaterMark(nullptr),
                   queue_depth, build_depth, retry_count);
#else
  hil_trace_eventf("health", "\"queue\":%u,\"build\":%u,\"retries\":%u",
                   queue_depth, build_depth, retry_count);
#endif
}

#endif
