#include "prometheus_handler.h"
#include "esphome/core/application.h"

namespace esphome {
namespace prometheus {

void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
  AsyncResponseStream *stream = req->beginResponseStream("text/plain; version=0.0.4; charset=utf-8");

#ifdef USE_SENSOR
  this->sensor_type_(stream);
  for (auto *obj : App.get_sensors())
    this->sensor_row_(stream, obj);
#endif

#ifdef USE_BINARY_SENSOR
  this->binary_sensor_type_(stream);
  for (auto *obj : App.get_binary_sensors())
    this->binary_sensor_row_(stream, obj);
#endif

#ifdef USE_FAN
  this->fan_type_(stream);
  for (auto *obj : App.get_fans())
    this->fan_row_(stream, obj);
#endif

#ifdef USE_LIGHT
  this->light_type_(stream);
  for (auto *obj : App.get_lights())
    this->light_row_(stream, obj);
#endif

#ifdef USE_COVER
  this->cover_type_(stream);
  for (auto *obj : App.get_covers())
    this->cover_row_(stream, obj);
#endif

#ifdef USE_SWITCH
  this->switch_type_(stream);
  for (auto *obj : App.get_switches())
    this->switch_row_(stream, obj);
#endif

#ifdef USE_LOCK
  this->lock_type_(stream);
  for (auto *obj : App.get_locks())
    this->lock_row_(stream, obj);
#endif

  req->send(stream);
}

std::string PrometheusHandler::relabel_id_(EntityBase *obj) {
  auto item = relabel_map_id_.find(obj);
  return item == relabel_map_id_.end() ? obj->get_object_id() : item->second;
}

std::string PrometheusHandler::relabel_name_(EntityBase *obj) {
  auto item = relabel_map_name_.find(obj);
  return item == relabel_map_name_.end() ? obj->get_name() : item->second;
}

// Type-specific implementation
#ifdef USE_SENSOR
void PrometheusHandler::sensor_type_(AsyncResponseStream *stream) {
  stream->print(F("#TYPE esphome_sensor_value gauge\n"));
  stream->print(F("#TYPE esphome_sensor_failed gauge\n"));
}
void PrometheusHandler::sensor_row_(AsyncResponseStream *stream, sensor::Sensor *obj) {
  if (obj->is_internal() && !this->include_internal_)
    return;
  if (!std::isnan(obj->state)) {
    // We have a valid value, output this value
    stream->print(F("esphome_sensor_failed{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} 0\n"));
    // Data itself
    stream->print(F("esphome_sensor_value{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\",unit=\""));
    stream->print(obj->get_unit_of_measurement().c_str());
    stream->print(F("\"} "));
    stream->print(value_accuracy_to_string(obj->state, obj->get_accuracy_decimals()).c_str());
    stream->print(F("\n"));
  } else {
    // Invalid state
    stream->print(F("esphome_sensor_failed{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} 1\n"));
  }
}
#endif

// Type-specific implementation
#ifdef USE_BINARY_SENSOR
void PrometheusHandler::binary_sensor_type_(AsyncResponseStream *stream) {
  stream->print(F("#TYPE esphome_binary_sensor_value gauge\n"));
  stream->print(F("#TYPE esphome_binary_sensor_failed gauge\n"));
}
void PrometheusHandler::binary_sensor_row_(AsyncResponseStream *stream, binary_sensor::BinarySensor *obj) {
  if (obj->is_internal() && !this->include_internal_)
    return;
  if (obj->has_state()) {
    // We have a valid value, output this value
    stream->print(F("esphome_binary_sensor_failed{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} 0\n"));
    // Data itself
    stream->print(F("esphome_binary_sensor_value{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} "));
    stream->print(obj->state);
    stream->print(F("\n"));
  } else {
    // Invalid state
    stream->print(F("esphome_binary_sensor_failed{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} 1\n"));
  }
}
#endif

#ifdef USE_FAN
void PrometheusHandler::fan_type_(AsyncResponseStream *stream) {
  stream->print(F("#TYPE esphome_fan_value gauge\n"));
  stream->print(F("#TYPE esphome_fan_failed gauge\n"));
  stream->print(F("#TYPE esphome_fan_speed gauge\n"));
  stream->print(F("#TYPE esphome_fan_oscillation gauge\n"));
}
void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) {
  if (obj->is_internal() && !this->include_internal_)
    return;
  stream->print(F("esphome_fan_failed{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\"} 0\n"));
  // Data itself
  stream->print(F("esphome_fan_value{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\"} "));
  stream->print(obj->state);
  stream->print(F("\n"));
  // Speed if available
  if (obj->get_traits().supports_speed()) {
    stream->print(F("esphome_fan_speed{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} "));
    stream->print(obj->speed);
    stream->print(F("\n"));
  }
  // Oscillation if available
  if (obj->get_traits().supports_oscillation()) {
    stream->print(F("esphome_fan_oscillation{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} "));
    stream->print(obj->oscillating);
    stream->print(F("\n"));
  }
}
#endif

#ifdef USE_LIGHT
void PrometheusHandler::light_type_(AsyncResponseStream *stream) {
  stream->print(F("#TYPE esphome_light_state gauge\n"));
  stream->print(F("#TYPE esphome_light_color gauge\n"));
  stream->print(F("#TYPE esphome_light_effect_active gauge\n"));
}
void PrometheusHandler::light_row_(AsyncResponseStream *stream, light::LightState *obj) {
  if (obj->is_internal() && !this->include_internal_)
    return;
  // State
  stream->print(F("esphome_light_state{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\"} "));
  stream->print(obj->remote_values.is_on());
  stream->print(F("\n"));
  // Brightness and RGBW
  light::LightColorValues color = obj->current_values;
  float brightness, r, g, b, w;
  color.as_brightness(&brightness);
  color.as_rgbw(&r, &g, &b, &w);
  stream->print(F("esphome_light_color{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\",channel=\"brightness\"} "));
  stream->print(brightness);
  stream->print(F("\n"));
  stream->print(F("esphome_light_color{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\",channel=\"r\"} "));
  stream->print(r);
  stream->print(F("\n"));
  stream->print(F("esphome_light_color{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\",channel=\"g\"} "));
  stream->print(g);
  stream->print(F("\n"));
  stream->print(F("esphome_light_color{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\",channel=\"b\"} "));
  stream->print(b);
  stream->print(F("\n"));
  stream->print(F("esphome_light_color{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\",channel=\"w\"} "));
  stream->print(w);
  stream->print(F("\n"));
  // Effect
  std::string effect = obj->get_effect_name();
  if (effect == "None") {
    stream->print(F("esphome_light_effect_active{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\",effect=\"None\"} 0\n"));
  } else {
    stream->print(F("esphome_light_effect_active{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\",effect=\""));
    stream->print(effect.c_str());
    stream->print(F("\"} 1\n"));
  }
}
#endif

#ifdef USE_COVER
void PrometheusHandler::cover_type_(AsyncResponseStream *stream) {
  stream->print(F("#TYPE esphome_cover_value gauge\n"));
  stream->print(F("#TYPE esphome_cover_failed gauge\n"));
}
void PrometheusHandler::cover_row_(AsyncResponseStream *stream, cover::Cover *obj) {
  if (obj->is_internal() && !this->include_internal_)
    return;
  if (!std::isnan(obj->position)) {
    // We have a valid value, output this value
    stream->print(F("esphome_cover_failed{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} 0\n"));
    // Data itself
    stream->print(F("esphome_cover_value{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} "));
    stream->print(obj->position);
    stream->print(F("\n"));
    if (obj->get_traits().get_supports_tilt()) {
      stream->print(F("esphome_cover_tilt{id=\""));
      stream->print(relabel_id_(obj).c_str());
      stream->print(F("\",name=\""));
      stream->print(relabel_name_(obj).c_str());
      stream->print(F("\"} "));
      stream->print(obj->tilt);
      stream->print(F("\n"));
    }
  } else {
    // Invalid state
    stream->print(F("esphome_cover_failed{id=\""));
    stream->print(relabel_id_(obj).c_str());
    stream->print(F("\",name=\""));
    stream->print(relabel_name_(obj).c_str());
    stream->print(F("\"} 1\n"));
  }
}
#endif

#ifdef USE_SWITCH
void PrometheusHandler::switch_type_(AsyncResponseStream *stream) {
  stream->print(F("#TYPE esphome_switch_value gauge\n"));
  stream->print(F("#TYPE esphome_switch_failed gauge\n"));
}
void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch *obj) {
  if (obj->is_internal() && !this->include_internal_)
    return;
  stream->print(F("esphome_switch_failed{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\"} 0\n"));
  // Data itself
  stream->print(F("esphome_switch_value{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\"} "));
  stream->print(obj->state);
  stream->print(F("\n"));
}
#endif

#ifdef USE_LOCK
void PrometheusHandler::lock_type_(AsyncResponseStream *stream) {
  stream->print(F("#TYPE esphome_lock_value gauge\n"));
  stream->print(F("#TYPE esphome_lock_failed gauge\n"));
}
void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) {
  if (obj->is_internal() && !this->include_internal_)
    return;
  stream->print(F("esphome_lock_failed{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\"} 0\n"));
  // Data itself
  stream->print(F("esphome_lock_value{id=\""));
  stream->print(relabel_id_(obj).c_str());
  stream->print(F("\",name=\""));
  stream->print(relabel_name_(obj).c_str());
  stream->print(F("\"} "));
  stream->print(obj->state);
  stream->print(F("\n"));
}
#endif

}  // namespace prometheus
}  // namespace esphome
