#pragma once

#include "esphome/core/component.h"
#include "esphome/core/log.h"
#include "esphome/components/ble_client/ble_client.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#include "esphome/components/web_server_base/web_server_base.h"
#include <nvs.h>
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <mutex>
#include <vector>

namespace esphome::jk_bms_selector {

namespace espbt = esphome::esp32_ble_tracker;

struct SeenDevice {
  uint64_t address;
  esp_ble_addr_type_t address_type;
  std::string mac;
  std::string name;
  int rssi;
  uint32_t seen_at;
  bool likely_jk;
};

class JkBmsSelector : public Component, public espbt::ESPBTDeviceListener, public AsyncWebHandler {
 public:
  void set_ble_client(ble_client::BLEClient *client) { client_ = client; }
  float get_setup_priority() const override { return setup_priority::AFTER_BLUETOOTH + 1.0f; }
  void setup() override;
  void loop() override;
  void dump_config() override;
  bool parse_device(const espbt::ESPBTDevice &device) override;
  bool canHandle(AsyncWebServerRequest *request) const override;
  void handleRequest(AsyncWebServerRequest *request) override;

 protected:
  static bool parse_mac_(const std::string &mac, uint64_t &value);
  bool save_(uint64_t address, esp_ble_addr_type_t type);
  void apply_(uint64_t address, esp_ble_addr_type_t type);
  std::string page_();
  static std::string escape_(const std::string &value);
  ble_client::BLEClient *client_{nullptr};
  std::mutex mutex_;
  std::vector<SeenDevice> devices_;
  uint64_t selected_{0};
  esp_ble_addr_type_t selected_type_{BLE_ADDR_TYPE_PUBLIC};
  uint64_t pending_{0};
  esp_ble_addr_type_t pending_type_{BLE_ADDR_TYPE_PUBLIC};
  bool pending_change_{false};
};

inline void JkBmsSelector::setup() {
  nvs_handle_t handle;
  if (nvs_open("jk_bms_select", NVS_READONLY, &handle) == ESP_OK) {
    uint64_t address = 0;
    uint8_t type = BLE_ADDR_TYPE_PUBLIC;
    if (nvs_get_u64(handle, "address", &address) == ESP_OK) {
      nvs_get_u8(handle, "addr_type", &type);
      selected_ = address;
      selected_type_ = static_cast<esp_ble_addr_type_t>(type);
    }
    nvs_close(handle);
  }
  if (selected_ != 0) {
    client_->set_address(selected_);
    client_->set_remote_addr_type(selected_type_);
  }
  web_server_base::global_web_server_base->add_handler(this);
}

inline void JkBmsSelector::loop() {
  uint64_t address = 0;
  esp_ble_addr_type_t type = BLE_ADDR_TYPE_PUBLIC;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    if (!pending_change_) return;
    address = pending_;
    type = pending_type_;
    pending_change_ = false;
  }
  apply_(address, type);
}

inline void JkBmsSelector::apply_(uint64_t address, esp_ble_addr_type_t type) {
  client_->set_enabled(false);
  client_->set_address(address);
  client_->set_remote_addr_type(type);
  selected_ = address;
  selected_type_ = type;
  client_->set_enabled(true);
  ESP_LOGI("jk_bms_selector", "Selected BLE device %s", client_->address_str());
}

inline bool JkBmsSelector::save_(uint64_t address, esp_ble_addr_type_t type) {
  nvs_handle_t handle;
  if (nvs_open("jk_bms_select", NVS_READWRITE, &handle) != ESP_OK) return false;
  esp_err_t err = nvs_set_u64(handle, "address", address);
  if (err == ESP_OK) err = nvs_set_u8(handle, "addr_type", static_cast<uint8_t>(type));
  if (err == ESP_OK) err = nvs_commit(handle);
  nvs_close(handle);
  return err == ESP_OK;
}

inline bool JkBmsSelector::parse_device(const espbt::ESPBTDevice &device) {
  SeenDevice item{device.address_uint64(), device.get_address_type(), device.address_str(), device.get_name(),
                  device.get_rssi(), millis(), false};
  for (const auto &uuid : device.get_service_uuids())
    if (uuid.contains(0xE0, 0xFF)) item.likely_jk = true;
  if (item.name.rfind("JK", 0) == 0 || item.name.rfind("603", 0) == 0) item.likely_jk = true;
  std::lock_guard<std::mutex> lock(mutex_);
  auto it = std::find_if(devices_.begin(), devices_.end(), [&](const SeenDevice &v) { return v.address == item.address; });
  if (it == devices_.end()) {
    if (devices_.size() >= 40) {
      it = std::min_element(devices_.begin(), devices_.end(), [](const SeenDevice &a, const SeenDevice &b) {
        return a.seen_at < b.seen_at;
      });
      *it = item;
    } else {
      devices_.push_back(item);
    }
  } else {
    *it = item;
  }
  return false;
}

inline bool JkBmsSelector::parse_mac_(const std::string &mac, uint64_t &value) {
  unsigned int b[6];
  char tail;
  if (std::sscanf(mac.c_str(), "%2x:%2x:%2x:%2x:%2x:%2x%c", &b[0], &b[1], &b[2], &b[3], &b[4], &b[5], &tail) != 6)
    return false;
  value = 0;
  for (auto byte : b) value = (value << 8) | byte;
  return value != 0;
}

inline std::string JkBmsSelector::escape_(const std::string &value) {
  std::string out;
  for (char c : value) {
    if (c == '&') out += "&amp;";
    else if (c == '<') out += "&lt;";
    else if (c == '>') out += "&gt;";
    else if (c == '\"') out += "&quot;";
    else out += c;
  }
  return out;
}

inline std::string JkBmsSelector::page_() {
  std::vector<SeenDevice> copy;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    copy = devices_;
  }
  std::sort(copy.begin(), copy.end(), [](const SeenDevice &a, const SeenDevice &b) {
    if (a.likely_jk != b.likely_jk) return a.likely_jk > b.likely_jk;
    return a.rssi > b.rssi;
  });
  std::string html = R"HTML(<!doctype html><html lang="ru"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>JK BMS setup</title><style>body{font:16px system-ui;max-width:850px;margin:30px auto;padding:0 14px;background:#111;color:#eee}a{color:#7cc4ff}.card{padding:16px;margin:10px 0;background:#222;border-radius:12px}button{padding:9px 14px;border:0;border-radius:8px;background:#1888e8;color:white;font-weight:600}.jk{border:1px solid #28a745}.muted{color:#aaa;font-size:14px}</style><h1>Выбор JK BMS</h1><p><a href="/">← Все метрики</a> · страница обновится через 8 секунд</p>)HTML";
  html += "<p>Выбрано: <b>" + std::string(selected_ ? client_->address_str() : "ещё не выбрано") + "</b></p>";
  if (copy.empty()) html += "<div class=card>Сканирование Bluetooth… Если список пуст, включите BMS и обновите страницу.</div>";
  for (const auto &d : copy) {
    html += "<div class=\"card" + std::string(d.likely_jk ? " jk" : "") + "\"><b>";
    html += escape_(d.name.empty() ? "Без имени" : d.name) + "</b><br><span class=muted>" + d.mac + " · RSSI " + std::to_string(d.rssi);
    if (d.likely_jk) html += " · вероятно JK BMS";
    html += "</span><form method=post action=/bms/select><input type=hidden name=mac value=\"" + d.mac + "\"><input type=hidden name=type value=\"" + std::to_string((int)d.address_type) + "\"><p><button>Подключить</button></p></form></div>";
  }
  html += "<script>setTimeout(()=>location.reload(),8000)</script></html>";
  return html;
}

inline bool JkBmsSelector::canHandle(AsyncWebServerRequest *request) const {
  char buffer[AsyncWebServerRequest::URL_BUF_SIZE];
  auto url = request->url_to(buffer);
  return url == "/bms" || url == "/bms/select";
}

inline void JkBmsSelector::handleRequest(AsyncWebServerRequest *request) {
  char buffer[AsyncWebServerRequest::URL_BUF_SIZE];
  const std::string url(request->url_to(buffer).c_str());
  if (request->method() == HTTP_POST && url == "/bms/select") {
    uint64_t address;
    const std::string mac = request->arg("mac");
    int type = std::atoi(request->arg("type").c_str());
    if (!parse_mac_(mac, address) || type < 0 || type > 3) {
      request->send(400, "text/plain; charset=utf-8", "Некорректный BLE-адрес");
      return;
    }
    if (!save_(address, static_cast<esp_ble_addr_type_t>(type))) {
      request->send(500, "text/plain; charset=utf-8", "Не удалось сохранить выбор");
      return;
    }
    {
      std::lock_guard<std::mutex> lock(mutex_);
      pending_ = address;
      pending_type_ = static_cast<esp_ble_addr_type_t>(type);
      pending_change_ = true;
    }
    auto *response = request->beginResponse(303, "text/plain", "Selected");
    response->addHeader("Location", "/bms");
    request->send(response);
    return;
  }
  if (request->method() != HTTP_GET) {
    request->send(405, "text/plain", "Method not allowed");
    return;
  }
  const std::string body = page_();
  request->send(200, "text/html; charset=utf-8", body.c_str());
}

inline void JkBmsSelector::dump_config() {
  ESP_LOGCONFIG("jk_bms_selector", "JK BMS selector: /bms, selected=%s", selected_ ? client_->address_str() : "none");
}

}  // namespace esphome::jk_bms_selector
