#include "mqtt_client.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "config.h"
#include "json_codec.h"

static WiFiClient wifi_client;
static PubSubClient mqtt_client(wifi_client);
static WifiMqttConfig g_cfg;
static String g_client_id;

static const char *fault_text(FaultType fault) {
  switch (fault) {
    case FaultType::MeterRead:
      return "meter";
    case FaultType::Decode:
      return "decode";
    case FaultType::LoraTx:
      return "loratx";
    case FaultType::TimeSync:
      return "timesync";
    default:
      return "none";
  }
}

void mqtt_init(const WifiMqttConfig &config, const char *device_id) {
  g_cfg = config;
  mqtt_client.setServer(config.mqtt_host.c_str(), config.mqtt_port);
  if (device_id && device_id[0] != '\0') {
    g_client_id = String("dd3-bridge-") + device_id;
  } else {
    g_client_id = String("dd3-bridge-") + String(random(0xffff), HEX);
  }
}

static bool mqtt_connect() {
  if (mqtt_client.connected()) {
    return true;
  }
  String client_id = g_client_id.length() > 0 ? g_client_id : String("dd3-bridge-") + String(random(0xffff), HEX);
  if (g_cfg.mqtt_user.length() > 0) {
    return mqtt_client.connect(client_id.c_str(), g_cfg.mqtt_user.c_str(), g_cfg.mqtt_pass.c_str());
  }
  return mqtt_client.connect(client_id.c_str());
}

void mqtt_loop() {
  if (!mqtt_connect()) {
    return;
  }
  mqtt_client.loop();
}

bool mqtt_is_connected() {
  return mqtt_client.connected();
}

bool mqtt_publish_state(const MeterData &data) {
  if (!mqtt_connect()) {
    return false;
  }
  String payload;
  if (!meterDataToJson(data, payload)) {
    return false;
  }
  String topic = String("smartmeter/") + data.device_id + "/state";
  return mqtt_client.publish(topic.c_str(), payload.c_str());
}

bool mqtt_publish_faults(const char *device_id, const FaultCounters &counters, FaultType last_error, uint32_t last_error_age_sec) {
  if (!device_id || device_id[0] == '\0') {
    return false;
  }
  if (!mqtt_connect()) {
    return false;
  }

  StaticJsonDocument<192> doc;
  doc["err_m"] = counters.meter_read_fail;
  doc["err_d"] = counters.decode_fail;
  doc["err_tx"] = counters.lora_tx_fail;
  doc["err_last"] = static_cast<uint8_t>(last_error);
  doc["err_last_text"] = fault_text(last_error);
  doc["err_last_age"] = last_error != FaultType::None ? last_error_age_sec : 0;

  String payload;
  size_t len = serializeJson(doc, payload);
  if (len == 0) {
    return false;
  }
  String topic = String("smartmeter/") + device_id + "/faults";
  return mqtt_client.publish(topic.c_str(), payload.c_str(), true);
}

static bool publish_discovery_sensor(const char *device_id, const char *key, const char *name, const char *unit, const char *device_class,
                                     const char *state_topic, const char *value_template) {
  StaticJsonDocument<256> doc;
  String unique_id = String("dd3_") + device_id + "_" + key;
  String sensor_name = String(device_id) + " " + name;

  doc["name"] = sensor_name;
  doc["state_topic"] = state_topic;
  doc["unique_id"] = unique_id;
  if (unit && unit[0] != '\0') {
    doc["unit_of_measurement"] = unit;
  }
  if (device_class && device_class[0] != '\0') {
    doc["device_class"] = device_class;
  }
  doc["value_template"] = value_template;

  JsonObject device = doc.createNestedObject("device");
  JsonArray identifiers = device.createNestedArray("identifiers");
  identifiers.add(String("dd3-") + device_id);
  device["name"] = String("DD3 ") + device_id;
  device["model"] = "DD3-LoRa-Bridge";
  device["manufacturer"] = "DD3";

  String payload;
  size_t len = serializeJson(doc, payload);
  if (len == 0) {
    return false;
  }

  String topic = String("homeassistant/sensor/") + device_id + "/" + key + "/config";
  return mqtt_client.publish(topic.c_str(), payload.c_str(), true);
}

bool mqtt_publish_discovery(const char *device_id) {
  if (!device_id || device_id[0] == '\0') {
    return false;
  }
  if (!mqtt_connect()) {
    return false;
  }

  String state_topic = String("smartmeter/") + device_id + "/state";
  bool ok = true;
  ok = ok && publish_discovery_sensor(device_id, "energy", "Energy", "kWh", "energy", state_topic.c_str(), "{{ value_json.e_kwh }}");
  ok = ok && publish_discovery_sensor(device_id, "power", "Power", "W", "power", state_topic.c_str(), "{{ value_json.p_w }}");
  ok = ok && publish_discovery_sensor(device_id, "p1", "Power L1", "W", "power", state_topic.c_str(), "{{ value_json.p1_w }}");
  ok = ok && publish_discovery_sensor(device_id, "p2", "Power L2", "W", "power", state_topic.c_str(), "{{ value_json.p2_w }}");
  ok = ok && publish_discovery_sensor(device_id, "p3", "Power L3", "W", "power", state_topic.c_str(), "{{ value_json.p3_w }}");
  ok = ok && publish_discovery_sensor(device_id, "bat_v", "Battery Voltage", "V", "voltage", state_topic.c_str(), "{{ value_json.bat_v }}");
  ok = ok && publish_discovery_sensor(device_id, "bat_pct", "Battery", "%", "battery", state_topic.c_str(), "{{ value_json.bat_pct }}");
  ok = ok && publish_discovery_sensor(device_id, "rssi", "LoRa RSSI", "dBm", "signal_strength", state_topic.c_str(), "{{ value_json.rssi }}");
  ok = ok && publish_discovery_sensor(device_id, "snr", "LoRa SNR", "dB", "", state_topic.c_str(), "{{ value_json.snr }}");

  String faults_topic = String("smartmeter/") + device_id + "/faults";
  ok = ok && publish_discovery_sensor(device_id, "err_m", "Meter Read Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_m }}");
  ok = ok && publish_discovery_sensor(device_id, "err_d", "Decode Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_d }}");
  ok = ok && publish_discovery_sensor(device_id, "err_tx", "LoRa TX Errors", "count", "", faults_topic.c_str(), "{{ value_json.err_tx }}");
  ok = ok && publish_discovery_sensor(device_id, "err_last", "Last Error Code", "", "", faults_topic.c_str(), "{{ value_json.err_last }}");
  ok = ok && publish_discovery_sensor(device_id, "err_last_text", "Last Error", "", "", faults_topic.c_str(), "{{ value_json.err_last_text }}");
  ok = ok && publish_discovery_sensor(device_id, "err_last_age", "Last Error Age", "s", "", faults_topic.c_str(), "{{ value_json.err_last_age }}");
  return ok;
}

#ifdef ENABLE_TEST_MODE
bool mqtt_publish_test(const char *device_id, const String &payload) {
  if (!mqtt_connect()) {
    return false;
  }
  String topic = String("smartmeter/") + device_id + "/test";
  return mqtt_client.publish(topic.c_str(), payload.c_str());
}
#endif
