/**
 * XIAO ESP32-C3 —— BLE -> 串口 桥接器
 *
 * 流程：
 *   1) C3 上电后持续扫描手环 Beetle 的 BLE 广播
 *   2) 从 manufacturer data 解析 HR/SpO2/temp
 *   3) 生成 JSON + '\n' 通过 Serial1 转发给 WROOM
 *
 * 接线：
 *   C3 D6(GPIO21,TX) -> WROOM GPIO39 (RX)
 *   C3 D7(GPIO20,RX) -> WROOM GPIO38 (TX)
 *   GND<->GND, 3V3<->3V3
 *
 * USB(Serial) 仅用于调试日志；与 WROOM 的数据走 Serial1。
 */
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include <BLEClient.h>

// ===== 必须与 Beetle 完全一致 =====
static BLEUUID SERVICE_UUID("12345678-1234-1234-1234-1234567890ab");
static BLEUUID CHAR_UUID("12345678-1234-1234-1234-1234567890ac");
static const char *TARGET_NAME = "BrainFog_Beetle";

// ===== 与 WROOM 的串口链路 =====
#define LINK_RX_PIN 20  // D7
#define LINK_TX_PIN 21  // D6
#define LINK_BAUD 115200
HardwareSerial &LINK = Serial1;

static volatile bool g_doConnect = false;
static volatile bool g_connected = false;
static volatile bool g_userEnabled = true;
static BLEAdvertisedDevice *g_target = nullptr;
static BLEAddress g_targetAddress((uint8_t *)"\0\0\0\0\0\0");
static uint8_t g_targetAddressType = 0;
static bool g_haveTargetAddress = false;
static BLERemoteCharacteristic *g_remoteChar = nullptr;
static BLEClient *g_client = nullptr;
static uint32_t g_lastScanMs = 0;
static uint32_t g_lastRxMs = 0;
static uint32_t g_lastStatusMs = 0;
static uint32_t g_lastScanDebugMs = 0;
static uint32_t g_lastConnectAttemptMs = 0;
static uint32_t g_lastLinkNotifyMs = 0;
static String g_linkLine;
static String g_usbLine;
static bool g_scanStarted = false;

static void notifyWroomLinkState_(const char *state) {
  if (state == nullptr || state[0] == '\0') {
    return;
  }
  LINK.print("BAND,LINK,");
  LINK.println(state);
}

static void notifyCurrentLinkState_() {
  if (g_connected) {
    notifyWroomLinkState_("UP");
  } else if (g_userEnabled) {
    notifyWroomLinkState_("SCAN");
  } else {
    notifyWroomLinkState_("DOWN");
  }
}

static void releaseTarget_() {
  if (g_target != nullptr) {
    delete g_target;
    g_target = nullptr;
  }
  g_haveTargetAddress = false;
}

static void releaseClient_() {
  g_remoteChar = nullptr;
  if (g_client != nullptr) {
    if (g_client->isConnected()) g_client->disconnect();
    delete g_client;
    g_client = nullptr;
  }
}

static void onNotify(BLERemoteCharacteristic *, uint8_t *data, size_t len, bool) {
  // 原样转发给 WROOM（一行一包）
  LINK.write(data, len);
  LINK.write('\n');
  g_lastRxMs = millis();
  // 调试回显
  Serial.write(data, len);
  Serial.println();
}

static bool parseBandAdvertisement_(BLEAdvertisedDevice &dev) {
  if (!dev.haveManufacturerData()) return false;
  String mfg = dev.getManufacturerData();
  if (mfg.length() < 10) return false;
  const uint8_t *p = reinterpret_cast<const uint8_t *>(mfg.c_str());
  if (p[0] != 0xFF || p[1] != 0xFF || p[2] != 'B' || p[3] != 'F') {
    return false;
  }

  const uint16_t seq = static_cast<uint16_t>(p[4]) | (static_cast<uint16_t>(p[5]) << 8);
  const int hr = p[6];
  const int spo2 = p[7];
  const int16_t temp10 = static_cast<int16_t>(static_cast<uint16_t>(p[8]) |
                                              (static_cast<uint16_t>(p[9]) << 8));
  const float temp = temp10 / 10.0f;

  char line[96];
  snprintf(line, sizeof(line), "{\"seq\":%u,\"hr\":%d,\"spo2\":%d,\"temp\":%.1f}",
           static_cast<unsigned>(seq), hr, spo2, temp);
  LINK.println(line);
  Serial.println(line);
  g_lastRxMs = millis();
  if (!g_connected) {
    g_connected = true;
    notifyWroomLinkState_("UP");
    Serial.println("[C3] band advertisement online");
  }
  return true;
}

class ClientCb : public BLEClientCallbacks {
  void onConnect(BLEClient *) override {}
  void onDisconnect(BLEClient *) override {
    g_connected = false;
    notifyWroomLinkState_("DOWN");
    Serial.println("[C3] BLE disconnected");
  }
};

class ScanCb : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice dev) override {
    if (g_doConnect) return;
    if (parseBandAdvertisement_(dev)) return;
    bool match = false;
    if (dev.haveName() && dev.getName() == TARGET_NAME) match = true;
    if (!match && dev.haveServiceUUID() && dev.isAdvertisingService(SERVICE_UUID)) match = true;
    if ((match || dev.haveName() || dev.haveServiceUUID()) && millis() - g_lastScanDebugMs > 250) {
      g_lastScanDebugMs = millis();
      Serial.printf("[C3] adv name=%s svc=%d match=%d addr=%s type=%u rssi=%d\n",
                    dev.haveName() ? dev.getName().c_str() : "",
                    dev.haveServiceUUID() ? 1 : 0, match ? 1 : 0,
                    dev.getAddress().toString().c_str(), dev.getAddressType(), dev.getRSSI());
    }
    if (!match) return;
    releaseTarget_();
    g_target = new BLEAdvertisedDevice(dev);
    g_targetAddress = dev.getAddress();
    g_targetAddressType = dev.getAddressType();
    g_haveTargetAddress = true;
    notifyWroomLinkState_("SCAN");
    Serial.printf("[C3] found BrainFog_Beetle addr=%s type=%u rssi=%d\n",
                  dev.getAddress().toString().c_str(), dev.getAddressType(), dev.getRSSI());
  }
};

static void startScan() {
  if (g_doConnect) return;
  BLEScan *scan = BLEDevice::getScan();
  if (scan->isScanning()) return;
  if (!g_scanStarted) {
    scan->setAdvertisedDeviceCallbacks(new ScanCb(), true);
    scan->setInterval(80);
    scan->setWindow(80);
    scan->setActiveScan(true);
    g_scanStarted = true;
  }
  scan->clearResults();
  Serial.println("[C3] scanning...");
  scan->start(1, false);  // 分段扫描；空闲时只缓存目标，收到命令后才连接
  g_lastScanMs = millis();
}

static void stopBandLink() {
  BLEDevice::getScan()->stop();
  BLEDevice::getScan()->clearResults();
  g_doConnect = false;
  g_connected = false;
  releaseClient_();
  releaseTarget_();
  Serial.println("[C3] band link stopped by WROOM");
}

static bool connectToBeetle() {
  if (!g_userEnabled) return false;
  if (!g_haveTargetAddress) return false;
  const uint32_t now = millis();
  if (g_lastConnectAttemptMs && now - g_lastConnectAttemptMs < 2500) {
    return false;
  }
  g_lastConnectAttemptMs = now;
  BLEScan *scan = BLEDevice::getScan();
  if (scan->isScanning()) {
    scan->stop();
    delay(300);
  }
  Serial.printf("[C3] connecting %s type=%u\n",
                g_targetAddress.toString().c_str(), g_targetAddressType);

  releaseClient_();
  g_client = BLEDevice::createClient();
  g_client->setClientCallbacks(new ClientCb());

  if (!g_client->connect(g_targetAddress, g_targetAddressType, 6000)) {
    Serial.println("[C3] connect failed");
    releaseClient_();
    releaseTarget_();
    notifyWroomLinkState_("SCAN");
    return false;
  }
  Serial.println("[C3] connected to GATT server");
  g_client->setMTU(185);

  BLERemoteService *svc = g_client->getService(SERVICE_UUID);
  if (svc == nullptr) {
    Serial.println("[C3] service not found");
    releaseClient_();
    releaseTarget_();
    return false;
  }
  Serial.println("[C3] service found");

  g_remoteChar = svc->getCharacteristic(CHAR_UUID);
  if (g_remoteChar == nullptr) {
    Serial.println("[C3] characteristic not found");
    releaseClient_();
    releaseTarget_();
    return false;
  }
  Serial.println("[C3] characteristic found");

  if (g_remoteChar->canNotify()) {
    g_remoteChar->registerForNotify(onNotify);
    Serial.println("[C3] notify registered");
  } else {
    Serial.println("[C3] characteristic cannot notify");
  }
  if (g_remoteChar->canRead()) {
    String v = g_remoteChar->readValue();
    if (v.length() > 0) onNotify(g_remoteChar, (uint8_t *)v.c_str(), v.length(), false);
  }

  g_connected = true;
  g_lastRxMs = millis();
  notifyWroomLinkState_("UP");
  Serial.println("[C3] connected + subscribed");
  releaseTarget_();
  return true;
}

static void handleLinkCommand(const String &raw) {
  String cmd = raw;
  cmd.trim();
  if (!cmd.length()) return;

  if (cmd == "BAND,CONNECT") {
    if (!g_userEnabled) {
      Serial.println("[C3] WROOM requested band connect");
    }
    g_userEnabled = true;
    notifyWroomLinkState_("SCAN");
    if (!g_connected && !g_doConnect) {
      startScan();
    }
    return;
  }

  if (cmd == "BAND,DISCONNECT") {
    // WROOM no longer owns the BLE connection. Keep this command as a debug
    // escape hatch only; normal operation is always-on auto reconnect.
    Serial.println("[C3] ignore WROOM disconnect (auto mode)");
    notifyCurrentLinkState_();
    return;
  }
}

static void pollLinkCommands() {
  while (LINK.available()) {
    char c = static_cast<char>(LINK.read());
    if (c == '\n' || c == '\r') {
      if (g_linkLine.length()) {
        handleLinkCommand(g_linkLine);
        g_linkLine = "";
      }
    } else {
      g_linkLine += c;
      if (g_linkLine.length() > 80) {
        g_linkLine = "";
      }
    }
  }
}

static void pollUsbDebugCommands() {
  while (Serial.available()) {
    char c = static_cast<char>(Serial.read());
    if (c == '\n' || c == '\r') {
      if (g_usbLine.length()) {
        Serial.printf("[C3] USB cmd: %s\n", g_usbLine.c_str());
        handleLinkCommand(g_usbLine);
        g_usbLine = "";
      }
    } else {
      g_usbLine += c;
      if (g_usbLine.length() > 80) {
        g_usbLine = "";
      }
    }
  }
}

void setup() {
  Serial.begin(115200);
  LINK.setRxBufferSize(256);
  LINK.begin(LINK_BAUD, SERIAL_8N1, LINK_RX_PIN, LINK_TX_PIN);
  delay(200);
  Serial.println("[C3] BLE->UART bridge boot");
  BLEDevice::init("BrainFog_C3_Bridge");
  notifyWroomLinkState_("SCAN");
  startScan();
  Serial.println("[C3] auto mode; scanning and connecting without WROOM command");
}

void loop() {
  pollLinkCommands();
  pollUsbDebugCommands();

  // Connectionless mode: no GATT connect. Data arrives through advertisements.

  // 持续分段扫描；数据通过广播包进来，不建立 GATT 连接。
  if (!g_doConnect) {
    BLEScan *scan = BLEDevice::getScan();
    if (!scan->isScanning() && millis() - g_lastScanMs > 300) {
      startScan();
      pollLinkCommands();
    }
  }

  // 心跳/掉线保护：连接中但 8s 没收到广播数据，回到扫描状态。
  if (g_connected && millis() - g_lastRxMs > 8000) {
    Serial.println("[C3] no band advertisement 8s -> scanning");
    g_connected = false;
    notifyWroomLinkState_("DOWN");
  }

  if (millis() - g_lastLinkNotifyMs > 2000) {
    g_lastLinkNotifyMs = millis();
    notifyCurrentLinkState_();
  }

  // 1s 一次状态日志
  if (millis() - g_lastStatusMs > 1000) {
    g_lastStatusMs = millis();
    if (!g_connected) {
      Serial.println("[C3] waiting for band...");
    }
  }

  delay(20);
}
