#include <Arduino.h>
#include <unity.h>

#include <ArduinoJson.h>

#include "config.h"
#include "data_model.h"
#include "dd3_legacy_core.h"
#include "ha_discovery_json.h"
#include "json_codec.h"

static void fill_state_sample(MeterData &data) {
  data = {};
  data.ts_utc = 1769905000;
  data.short_id = 0xF19C;
  strncpy(data.device_id, "dd3-F19C", sizeof(data.device_id));
  data.energy_total_kwh = 1234.5678f;
  data.total_power_w = 321.6f;
  data.phase_power_w[0] = 100.4f;
  data.phase_power_w[1] = 110.4f;
  data.phase_power_w[2] = 110.8f;
  data.battery_voltage_v = 3.876f;
  data.battery_percent = 77;
  data.link_valid = true;
  data.link_rssi_dbm = -71;
  data.link_snr_db = 7.25f;
  data.err_meter_read = 1;
  data.err_decode = 2;
  data.err_lora_tx = 3;
  data.last_error = FaultType::Decode;
  data.rx_reject_reason = static_cast<uint8_t>(RxRejectReason::CrcFail);
}

static void test_state_json_required_keys_and_stability() {
  MeterData data = {};
  fill_state_sample(data);

  String out_json;
  TEST_ASSERT_TRUE(meterDataToJson(data, out_json));

  StaticJsonDocument<512> doc;
  DeserializationError err = deserializeJson(doc, out_json);
  TEST_ASSERT_TRUE(err == DeserializationError::Ok);

  const char *required_keys[] = {
      "id", "ts", "e_kwh", "p_w", "p1_w", "p2_w", "p3_w",
      "bat_v", "bat_pct", "rssi", "snr", "err_m", "err_d",
      "err_tx", "err_last", "rx_reject", "rx_reject_text"};
  for (size_t i = 0; i < (sizeof(required_keys) / sizeof(required_keys[0])); ++i) {
    TEST_ASSERT_TRUE_MESSAGE(doc.containsKey(required_keys[i]), required_keys[i]);
  }

  TEST_ASSERT_EQUAL_STRING("F19C", doc["id"] | "");
  TEST_ASSERT_EQUAL_UINT32(data.ts_utc, doc["ts"] | 0U);
  TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(FaultType::Decode), doc["err_last"] | 0U);
  TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(RxRejectReason::CrcFail), doc["rx_reject"] | 0U);
  TEST_ASSERT_EQUAL_STRING("crc_fail", doc["rx_reject_text"] | "");

  TEST_ASSERT_FALSE(doc.containsKey("energy_total_kwh"));
  TEST_ASSERT_FALSE(doc.containsKey("power_w"));
  TEST_ASSERT_FALSE(doc.containsKey("battery_voltage"));
}

static void test_state_json_optional_keys_when_not_available() {
  MeterData data = {};
  fill_state_sample(data);
  data.link_valid = false;
  data.err_meter_read = 0;
  data.err_decode = 0;
  data.err_lora_tx = 0;
  data.rx_reject_reason = static_cast<uint8_t>(RxRejectReason::None);

  String out_json;
  TEST_ASSERT_TRUE(meterDataToJson(data, out_json));

  StaticJsonDocument<512> doc;
  DeserializationError err = deserializeJson(doc, out_json);
  TEST_ASSERT_TRUE(err == DeserializationError::Ok);

  TEST_ASSERT_FALSE(doc.containsKey("rssi"));
  TEST_ASSERT_FALSE(doc.containsKey("snr"));
  TEST_ASSERT_FALSE(doc.containsKey("err_m"));
  TEST_ASSERT_FALSE(doc.containsKey("err_d"));
  TEST_ASSERT_FALSE(doc.containsKey("err_tx"));
  TEST_ASSERT_EQUAL_STRING("none", doc["rx_reject_text"] | "");
}

static void test_ha_discovery_manufacturer_and_key_stability() {
  String payload;
  TEST_ASSERT_TRUE(ha_build_discovery_sensor_payload(
      "dd3-F19C", "energy", "Energy", "kWh", "energy",
      "smartmeter/dd3-F19C/state", "{{ value_json.e_kwh }}",
      HA_MANUFACTURER, payload));

  StaticJsonDocument<384> doc;
  DeserializationError err = deserializeJson(doc, payload);
  TEST_ASSERT_TRUE(err == DeserializationError::Ok);

  TEST_ASSERT_TRUE(doc.containsKey("name"));
  TEST_ASSERT_TRUE(doc.containsKey("state_topic"));
  TEST_ASSERT_TRUE(doc.containsKey("unique_id"));
  TEST_ASSERT_TRUE(doc.containsKey("value_template"));
  TEST_ASSERT_TRUE(doc.containsKey("device"));

  TEST_ASSERT_EQUAL_STRING("dd3-F19C_energy", doc["unique_id"] | "");
  TEST_ASSERT_EQUAL_STRING("smartmeter/dd3-F19C/state", doc["state_topic"] | "");
  TEST_ASSERT_EQUAL_STRING("{{ value_json.e_kwh }}", doc["value_template"] | "");

  JsonObject device = doc["device"].as<JsonObject>();
  TEST_ASSERT_TRUE(device.containsKey("identifiers"));
  TEST_ASSERT_TRUE(device.containsKey("name"));
  TEST_ASSERT_TRUE(device.containsKey("model"));
  TEST_ASSERT_TRUE(device.containsKey("manufacturer"));
  TEST_ASSERT_EQUAL_STRING("DD3-LoRa-Bridge", device["model"] | "");
  TEST_ASSERT_EQUAL_STRING("AcidBurns", device["manufacturer"] | "");
  TEST_ASSERT_EQUAL_STRING("dd3-F19C", device["name"] | "");
  TEST_ASSERT_EQUAL_STRING("dd3-F19C", device["identifiers"][0] | "");
}

void setup() {
  dd3_legacy_core_force_link();
  UNITY_BEGIN();
  RUN_TEST(test_state_json_required_keys_and_stability);
  RUN_TEST(test_state_json_optional_keys_when_not_available);
  RUN_TEST(test_ha_discovery_manufacturer_and_key_stability);
  UNITY_END();
}

void loop() {}
