#include "json_codec.h"
#include <ArduinoJson.h>
#include <limits.h>
#include <math.h>
#include "config.h"
#include "power_manager.h"

static float round2(float value) {
  if (isnan(value)) {
    return value;
  }
  return roundf(value * 100.0f) / 100.0f;
}

static uint32_t kwh_to_wh(float value) {
  if (isnan(value)) {
    return 0;
  }
  double wh = static_cast<double>(value) * 1000.0;
  if (wh < 0.0) {
    wh = 0.0;
  }
  if (wh > static_cast<double>(UINT32_MAX)) {
    wh = static_cast<double>(UINT32_MAX);
  }
  return static_cast<uint32_t>(llround(wh));
}

static int32_t round_to_i32(float value) {
  if (isnan(value)) {
    return 0;
  }
  long rounded = lroundf(value);
  if (rounded > INT32_MAX) {
    return INT32_MAX;
  }
  if (rounded < INT32_MIN) {
    return INT32_MIN;
  }
  return static_cast<int32_t>(rounded);
}

static const char *short_id_from_device_id(const char *device_id) {
  if (!device_id) {
    return "";
  }
  size_t len = strlen(device_id);
  if (len >= 4) {
    return device_id + (len - 4);
  }
  return device_id;
}

static void sender_label_from_short_id(uint16_t short_id, char *out, size_t out_len) {
  if (!out || out_len == 0) {
    return;
  }
  for (uint8_t i = 0; i < NUM_SENDERS; ++i) {
    if (EXPECTED_SENDER_IDS[i] == short_id) {
      snprintf(out, out_len, "s%02u", static_cast<unsigned>(i + 1));
      return;
    }
  }
  snprintf(out, out_len, "s00");
}

static uint16_t short_id_from_sender_label(const char *sender_label) {
  if (!sender_label || strlen(sender_label) < 2 || sender_label[0] != 's') {
    return 0;
  }
  char *end = nullptr;
  long idx = strtol(sender_label + 1, &end, 10);
  if (end == sender_label + 1 || idx <= 0 || idx > NUM_SENDERS) {
    return 0;
  }
  return EXPECTED_SENDER_IDS[idx - 1];
}

static void format_float_2(char *buf, size_t buf_len, float value) {
  if (!buf || buf_len == 0) {
    return;
  }
  if (isnan(value)) {
    snprintf(buf, buf_len, "null");
    return;
  }
  snprintf(buf, buf_len, "%.2f", round2(value));
}

bool meterDataToJson(const MeterData &data, String &out_json) {
  StaticJsonDocument<256> doc;
  doc["id"] = short_id_from_device_id(data.device_id);
  doc["ts"] = data.ts_utc;
  char buf[16];
  format_float_2(buf, sizeof(buf), data.energy_total_kwh);
  doc["e_kwh"] = serialized(buf);
  format_float_2(buf, sizeof(buf), data.total_power_w);
  doc["p_w"] = serialized(buf);
  format_float_2(buf, sizeof(buf), data.phase_power_w[0]);
  doc["p1_w"] = serialized(buf);
  format_float_2(buf, sizeof(buf), data.phase_power_w[1]);
  doc["p2_w"] = serialized(buf);
  format_float_2(buf, sizeof(buf), data.phase_power_w[2]);
  doc["p3_w"] = serialized(buf);
  format_float_2(buf, sizeof(buf), data.battery_voltage_v);
  doc["bat_v"] = serialized(buf);
  doc["bat_pct"] = data.battery_percent;
  if (data.link_valid) {
    doc["rssi"] = data.link_rssi_dbm;
    doc["snr"] = data.link_snr_db;
  }
  if (data.err_meter_read > 0) {
    doc["err_m"] = data.err_meter_read;
  }
  if (data.err_decode > 0) {
    doc["err_d"] = data.err_decode;
  }
  if (data.err_lora_tx > 0) {
    doc["err_tx"] = data.err_lora_tx;
  }
  doc["err_last"] = static_cast<uint8_t>(data.last_error);
  doc["rx_reject"] = data.rx_reject_reason;
  doc["rx_reject_text"] = rx_reject_reason_text(static_cast<RxRejectReason>(data.rx_reject_reason));

  out_json = "";
  size_t len = serializeJson(doc, out_json);
  return len > 0 && len < 256;
}

bool jsonToMeterData(const String &json, MeterData &data) {
  StaticJsonDocument<256> doc;
  DeserializationError err = deserializeJson(doc, json);
  if (err) {
    return false;
  }

  const char *id = doc["id"] | "";
  if (strlen(id) == 4) {
    snprintf(data.device_id, sizeof(data.device_id), "dd3-%s", id);
  } else {
    strncpy(data.device_id, id, sizeof(data.device_id));
  }
  data.device_id[sizeof(data.device_id) - 1] = '\0';

  data.ts_utc = doc["ts"] | 0;
  data.energy_total_kwh = doc["e_kwh"] | NAN;
  data.total_power_w = doc["p_w"] | NAN;
  data.phase_power_w[0] = doc["p1_w"] | NAN;
  data.phase_power_w[1] = doc["p2_w"] | NAN;
  data.phase_power_w[2] = doc["p3_w"] | NAN;
  data.battery_voltage_v = doc["bat_v"] | NAN;
  if (doc["bat_pct"].isNull() && !isnan(data.battery_voltage_v)) {
    data.battery_percent = battery_percent_from_voltage(data.battery_voltage_v);
  } else {
    data.battery_percent = doc["bat_pct"] | 0;
  }
  data.valid = true;
  data.link_valid = false;
  data.link_rssi_dbm = 0;
  data.link_snr_db = NAN;
  data.err_meter_read = doc["err_m"] | 0;
  data.err_decode = doc["err_d"] | 0;
  data.err_lora_tx = doc["err_tx"] | 0;
  data.last_error = static_cast<FaultType>(doc["err_last"] | 0);
  data.rx_reject_reason = static_cast<uint8_t>(doc["rx_reject"] | 0);

  if (strlen(data.device_id) >= 8) {
    const char *suffix = data.device_id + strlen(data.device_id) - 4;
    data.short_id = static_cast<uint16_t>(strtoul(suffix, nullptr, 16));
  }

  return true;
}

bool meterBatchToJson(const MeterData *samples, size_t count, uint16_t batch_id, String &out_json, const FaultCounters *faults, FaultType last_error) {
  if (!samples || count == 0) {
    return false;
  }

  DynamicJsonDocument doc(8192);
  doc["schema"] = 1;
  char sender_label[8] = {};
  sender_label_from_short_id(samples[count - 1].short_id, sender_label, sizeof(sender_label));
  doc["sender"] = sender_label;
  doc["batch_id"] = batch_id;
  doc["t0"] = samples[0].ts_utc;
  doc["t_first"] = samples[0].ts_utc;
  doc["t_last"] = samples[count - 1].ts_utc;
  uint32_t dt_s = METER_SAMPLE_INTERVAL_MS / 1000;
  doc["dt_s"] = dt_s > 0 ? dt_s : 1;
  doc["n"] = static_cast<uint32_t>(count);
  if (faults) {
    if (faults->meter_read_fail > 0) {
      doc["err_m"] = faults->meter_read_fail;
    }
    if (faults->lora_tx_fail > 0) {
      doc["err_tx"] = faults->lora_tx_fail;
    }
  }
  doc["err_last"] = static_cast<uint8_t>(last_error);
  if (!isnan(samples[count - 1].battery_voltage_v)) {
    char bat_buf[16];
    format_float_2(bat_buf, sizeof(bat_buf), samples[count - 1].battery_voltage_v);
    doc["bat_v"] = serialized(bat_buf);
  }

  JsonArray energy = doc.createNestedArray("e_wh");
  JsonArray p_w = doc.createNestedArray("p_w");
  JsonArray p1_w = doc.createNestedArray("p1_w");
  JsonArray p2_w = doc.createNestedArray("p2_w");
  JsonArray p3_w = doc.createNestedArray("p3_w");
  for (size_t i = 0; i < count; ++i) {
    energy.add(kwh_to_wh(samples[i].energy_total_kwh));
    p_w.add(round_to_i32(samples[i].total_power_w));
    p1_w.add(round_to_i32(samples[i].phase_power_w[0]));
    p2_w.add(round_to_i32(samples[i].phase_power_w[1]));
    p3_w.add(round_to_i32(samples[i].phase_power_w[2]));
  }

  out_json = "";
  size_t len = serializeJson(doc, out_json);
  return len > 0;
}

bool jsonToMeterBatch(const String &json, MeterData *out_samples, size_t max_count, size_t &out_count) {
  out_count = 0;
  if (!out_samples || max_count == 0) {
    return false;
  }

  DynamicJsonDocument doc(8192);
  DeserializationError err = deserializeJson(doc, json);
  if (err) {
    return false;
  }

  const char *id = doc["id"] | "";
  const char *sender = doc["sender"] | "";
  uint32_t err_m = doc["err_m"] | 0;
  uint32_t err_tx = doc["err_tx"] | 0;
  FaultType last_error = static_cast<FaultType>(doc["err_last"] | 0);
  float bat_v = doc["bat_v"] | NAN;

  if (!doc["schema"].isNull()) {
    if ((doc["schema"] | 0) != 1) {
      return false;
    }
    size_t count = doc["n"] | 0;
    if (count == 0) {
      return false;
    }
    if (count > max_count) {
      count = max_count;
    }

    uint32_t t0 = doc["t0"] | 0;
    uint32_t t_first = doc["t_first"] | t0;
    uint32_t t_last = doc["t_last"] | t_first;
    uint32_t dt_s = doc["dt_s"] | 1;
    JsonArray energy = doc["e_wh"].as<JsonArray>();
    JsonArray p_w = doc["p_w"].as<JsonArray>();
    JsonArray p1_w = doc["p1_w"].as<JsonArray>();
    JsonArray p2_w = doc["p2_w"].as<JsonArray>();
    JsonArray p3_w = doc["p3_w"].as<JsonArray>();

    for (size_t idx = 0; idx < count; ++idx) {
      MeterData &data = out_samples[idx];
      data = {};
      uint16_t short_id = short_id_from_sender_label(sender);
      if (short_id != 0) {
        snprintf(data.device_id, sizeof(data.device_id), "dd3-%04X", short_id);
        data.short_id = short_id;
      } else if (id[0] != '\0') {
        strncpy(data.device_id, id, sizeof(data.device_id));
        data.device_id[sizeof(data.device_id) - 1] = '\0';
      } else {
        snprintf(data.device_id, sizeof(data.device_id), "dd3-0000");
      }

      if (count > 1 && t_last >= t_first) {
        uint32_t span = t_last - t_first;
        uint32_t step = span / static_cast<uint32_t>(count - 1);
        data.ts_utc = t_first + static_cast<uint32_t>(idx) * step;
      } else {
        data.ts_utc = t0 + static_cast<uint32_t>(idx) * dt_s;
      }
      data.energy_total_kwh = static_cast<float>((energy[idx] | 0)) / 1000.0f;
      data.total_power_w = static_cast<float>(p_w[idx] | 0);
      data.phase_power_w[0] = static_cast<float>(p1_w[idx] | 0);
      data.phase_power_w[1] = static_cast<float>(p2_w[idx] | 0);
      data.phase_power_w[2] = static_cast<float>(p3_w[idx] | 0);
      data.battery_voltage_v = bat_v;
      if (!isnan(bat_v)) {
        data.battery_percent = battery_percent_from_voltage(bat_v);
      } else {
        data.battery_percent = 0;
      }
      data.valid = true;
      data.link_valid = false;
      data.link_rssi_dbm = 0;
      data.link_snr_db = NAN;
      data.err_meter_read = err_m;
      data.err_decode = 0;
      data.err_lora_tx = err_tx;
      data.last_error = last_error;

      if (data.short_id == 0 && strlen(data.device_id) >= 8) {
        const char *suffix = data.device_id + strlen(data.device_id) - 4;
        data.short_id = static_cast<uint16_t>(strtoul(suffix, nullptr, 16));
      }
    }

    out_count = count;
    return count > 0;
  }

  return false;
}
