#include "meter_driver.h"
#include "config.h"
#include "hil_trace.h"
#include "meter_parser.h"

#include <math.h>

static constexpr uint32_t METER_FRAME_TIMEOUT_MS = 1500;
static constexpr size_t METER_UART_RX_BUFFER_BYTES = 8192;

static MeterFrameCollector g_collector = {};
static uint32_t g_bytes_rx = 0;
static uint32_t g_frames_ok = 0;
static uint32_t g_frames_parse_fail = 0;
static uint32_t g_rx_overflow = 0;
static uint32_t g_rx_timeout = 0;
static volatile uint32_t g_uart_buffer_full = 0;
static volatile uint32_t g_uart_fifo_overflow = 0;
static volatile uint32_t g_uart_frame_error = 0;
static volatile uint32_t g_uart_parity_error = 0;
static uint32_t g_last_log_ms = 0;
static uint32_t g_last_good_frame_ms = 0;
#if ENABLE_HIL_TRACE
static bool g_meter_seconds_seen = false;
static uint32_t g_last_meter_seconds = 0;
#endif

void meter_init() {
  meter_frame_collector_init(g_collector);
#ifdef ARDUINO_ARCH_ESP32
  Serial2.setRxBufferSize(METER_UART_RX_BUFFER_BYTES);
#endif
  Serial2.begin(9600, SERIAL_7E1, PIN_METER_RX, -1);
#ifdef ARDUINO_ARCH_ESP32
  Serial2.onReceiveError([](hardwareSerial_error_t error) {
    if (error == UART_BUFFER_FULL_ERROR) {
      g_uart_buffer_full = g_uart_buffer_full + 1;
    } else if (error == UART_FIFO_OVF_ERROR) {
      g_uart_fifo_overflow = g_uart_fifo_overflow + 1;
    } else if (error == UART_FRAME_ERROR) {
      g_uart_frame_error = g_uart_frame_error + 1;
    } else if (error == UART_PARITY_ERROR) {
      g_uart_parity_error = g_uart_parity_error + 1;
    }
  });
#endif
}

static void meter_debug_log() {
  if (!SERIAL_DEBUG_MODE) return;
  uint32_t now_ms = millis();
  if (now_ms - g_last_log_ms < 60000) return;
  g_last_log_ms = now_ms;
  Serial.printf("meter: ok=%lu parse_fail=%lu frame_ovf=%lu timeout=%lu bytes=%lu uart_full=%lu uart_fifo_ovf=%lu uart_frame=%lu uart_parity=%lu\n",
                static_cast<unsigned long>(g_frames_ok),
                static_cast<unsigned long>(g_frames_parse_fail),
                static_cast<unsigned long>(g_rx_overflow),
                static_cast<unsigned long>(g_rx_timeout),
                static_cast<unsigned long>(g_bytes_rx),
                static_cast<unsigned long>(g_uart_buffer_full),
                static_cast<unsigned long>(g_uart_fifo_overflow),
                static_cast<unsigned long>(g_uart_frame_error),
                static_cast<unsigned long>(g_uart_parity_error));
}

void meter_get_stats(MeterDriverStats &out) {
  out.frames_ok = g_frames_ok;
  out.frames_parse_fail = g_frames_parse_fail;
  out.rx_overflow = g_rx_overflow;
  out.rx_timeout = g_rx_timeout;
  out.bytes_rx = g_bytes_rx;
  out.last_rx_ms = g_collector.last_rx_ms;
  out.last_good_frame_ms = g_last_good_frame_ms;
  out.uart_buffer_full = g_uart_buffer_full;
  out.uart_fifo_overflow = g_uart_fifo_overflow;
  out.uart_frame_error = g_uart_frame_error;
  out.uart_parity_error = g_uart_parity_error;
}

bool meter_poll_frame(const char *&frame, size_t &len) {
  frame = nullptr;
  len = 0;
  uint32_t now_ms = millis();
  MeterFrameEvent timeout_event = meter_frame_collector_check_timeout(
      g_collector, now_ms, METER_FRAME_TIMEOUT_MS);
  if (timeout_event == MeterFrameEvent::Timeout) {
    g_rx_timeout++;
    hil_trace_event("meter_frame", "\"ok\":false,\"classification\":\"timeout\"");
  }

  while (Serial2.available()) {
    char byte = static_cast<char>(Serial2.read());
    g_bytes_rx++;
    MeterFrameEvent event = meter_frame_collector_feed(g_collector, byte, millis());
    if (event == MeterFrameEvent::Resync) {
      hil_trace_event("meter_resync", "\"reason\":\"new_start\"");
    } else if (event == MeterFrameEvent::Overflow) {
      g_rx_overflow++;
      hil_trace_event("meter_frame", "\"ok\":false,\"classification\":\"oversized\"");
    } else if (event == MeterFrameEvent::Complete) {
      frame = g_collector.buffer;
      len = g_collector.length;
      meter_debug_log();
      return true;
    }
  }

  meter_debug_log();
  return false;
}

bool meter_parse_frame(const char *frame, size_t len, MeterData &data) {
  MeterParseResult parsed = {};
  bool ok = meter_parser_parse(frame, len, parsed);
  data.energy_total_kwh = parsed.energy_total_kwh;
  data.total_power_w = parsed.total_power_w;
  data.phase_power_w[0] = parsed.phase_power_w[0];
  data.phase_power_w[1] = parsed.phase_power_w[1];
  data.phase_power_w[2] = parsed.phase_power_w[2];
  data.valid = ok;
  if (ok) {
    g_frames_ok++;
    g_last_good_frame_ms = millis();
  } else {
    g_frames_parse_fail++;
  }

#if ENABLE_HIL_TRACE
  if (parsed.meter_seconds_valid) {
    if (g_meter_seconds_seen && parsed.meter_seconds < g_last_meter_seconds) {
      hil_trace_eventf("meter_time", "\"classification\":\"rollback\",\"meter_seconds\":%lu,\"previous\":%lu",
                       static_cast<unsigned long>(parsed.meter_seconds),
                       static_cast<unsigned long>(g_last_meter_seconds));
    } else if (g_meter_seconds_seen && parsed.meter_seconds - g_last_meter_seconds > 120) {
      hil_trace_eventf("meter_time", "\"classification\":\"jump\",\"meter_seconds\":%lu,\"previous\":%lu",
                       static_cast<unsigned long>(parsed.meter_seconds),
                       static_cast<unsigned long>(g_last_meter_seconds));
    }
    g_last_meter_seconds = parsed.meter_seconds;
    g_meter_seconds_seen = true;
  }
#endif

  hil_trace_eventf("meter_frame", "\"ok\":%s,\"classification\":\"%s\",\"len\":%u,\"fields\":%u,\"meter_seconds\":%lu,\"seconds_valid\":%s",
                   ok ? "true" : "false", meter_parse_status_text(parsed.status),
                   static_cast<unsigned>(len), static_cast<unsigned>(parsed.fields_mask),
                   static_cast<unsigned long>(parsed.meter_seconds),
                   parsed.meter_seconds_valid ? "true" : "false");
  return ok;
}

bool meter_read(MeterData &data) {
  data.energy_total_kwh = NAN;
  data.total_power_w = NAN;
  data.phase_power_w[0] = NAN;
  data.phase_power_w[1] = NAN;
  data.phase_power_w[2] = NAN;
  data.valid = false;

  const char *frame = nullptr;
  size_t len = 0;
  if (!meter_poll_frame(frame, len)) return false;
  return meter_parse_frame(frame, len, data);
}
