#include "sh_core.h"

// ---------------------- weak device hooks (можно переопределить в прошивке) ----------------------
__attribute__((weak)) void appendStatusJsonFields(String &json) { (void)json; }
__attribute__((weak)) void appendAboutJsonFields(String &json)  { (void)json; }

__attribute__((weak)) bool deviceHandleAction(const String &action,
                                              const String &paramsJson,
                                              String &errorCode,
                                              String &errorMessage)
{
  (void)action;
  (void)paramsJson;
  errorCode = "IllegalActionOrParams";
  errorMessage = "Action not implemented";
  return false;
}

__attribute__((weak)) void deviceHandleReset() {}

// ---------------------- helpers ----------------------
static String deviceModeToString(DeviceMode m) {
  switch (m) {
    case DEVICE_MODE_SETUP:    return F("setup");
    case DEVICE_MODE_NORMAL:   return F("normal");
    case DEVICE_MODE_ERROR:    return F("error");
    case DEVICE_MODE_UPDATING: return F("updating");
  }
  return F("error");
}

static void sendJson(int code, const String &body) {
  server.sendHeader(F("Connection"), F("close"));
  server.sendHeader(F("Content-Length"), String(body.length()));
  server.send(code, F("application/json; charset=utf-8"), body);
  server.client().stop();
}

static String extractJsonStringValue(const String &body, const String &key) {
  String pattern = "\"" + key + "\"";
  int keyIndex = body.indexOf(pattern);
  if (keyIndex < 0) return "";

  int colonIndex = body.indexOf(':', keyIndex);
  if (colonIndex < 0) return "";

  int firstQuote = body.indexOf('"', colonIndex + 1);
  if (firstQuote < 0) return "";

  int secondQuote = body.indexOf('"', firstQuote + 1);
  if (secondQuote < 0) return "";

  return body.substring(firstQuote + 1, secondQuote);
}

static String extractJsonObject(const String &body, const String &key) {
  String pattern = "\"" + key + "\"";
  int keyIndex = body.indexOf(pattern);
  if (keyIndex < 0) return "";

  int colonIndex = body.indexOf(':', keyIndex);
  if (colonIndex < 0) return "";

  int braceIndex = body.indexOf('{', colonIndex);
  if (braceIndex < 0) return "";

  int depth = 0;
  for (int i = braceIndex; i < (int)body.length(); i++) {
    char c = body[i];
    if (c == '{') depth++;
    else if (c == '}') {
      depth--;
      if (depth == 0) {
        return body.substring(braceIndex, i + 1);
      }
    }
  }
  return "";
}

static String extractJsonArray(const String &body, const String &key) {
  String pattern = "\"" + key + "\"";
  int keyIndex = body.indexOf(pattern);
  if (keyIndex < 0) return "";

  int colonIndex = body.indexOf(':', keyIndex);
  if (colonIndex < 0) return "";

  int brIndex = body.indexOf('[', colonIndex);
  if (brIndex < 0) return "";

  int depth = 0;
  for (int i = brIndex; i < (int)body.length(); i++) {
    char c = body[i];
    if (c == '[') depth++;
    else if (c == ']') {
      depth--;
      if (depth == 0) return body.substring(brIndex, i + 1);
    }
  }
  return "";
}

static bool parseByteArray32(const String &arrJson, uint8_t *out32, String &err) {
  // ожидаем строку вида: [1, 2, 255, ...]
  int n = arrJson.length();
  if (n < 2 || arrJson[0] != '[' || arrJson[n - 1] != ']') {
    err = F("schema must be array");
    return false;
  }

  uint8_t count = 0;
  long value = -1;
  bool inNumber = false;

  for (int i = 1; i < n - 1; i++) {
    char c = arrJson[i];

    if (c >= '0' && c <= '9') {
      if (!inNumber) { inNumber = true; value = 0; }
      value = value * 10 + (c - '0');
      if (value > 255) {
        err = F("schema values must be 0..255");
        return false;
      }
      continue;
    }

    if (c == '-' ) { // отрицательные не допускаем
      err = F("schema values must be 0..255");
      return false;
    }

    // разделители
    if (c == ',' || c == ' ' || c == '\n' || c == '\r' || c == '\t') {
      if (inNumber) {
        if (count >= 32) { err = F("schema must contain exactly 32 values"); return false; }
        out32[count++] = (uint8_t)value;
        inNumber = false;
        value = -1;
      }
      continue;
    }

    // любой другой символ — ошибка
    err = F("schema contains invalid characters");
    return false;
  }

  // хвост
  if (inNumber) {
    if (count >= 32) { err = F("schema must contain exactly 32 values"); return false; }
    out32[count++] = (uint8_t)value;
  }

  if (count != 32) {
    err = F("schema must contain exactly 32 values");
    return false;
  }

  return true;
}


// ---------------------- auth ----------------------
static bool hasValidToken() {
  if (authToken.length() == 0) return false;

  String header = server.header(F("Authorization"));
  if (!header.startsWith(F("Bearer "))) return false;

  String token = header.substring(7);
  token.trim();
  if (token != authToken) return false;

  if (serverBaseUrl.length() > 0) {
    IPAddress remote = server.client().remoteIP();
    String ipStr = remote.toString();

    // if (ipStr != serverBaseUrl) {
    //   Serial.print(F("Auth IP mismatch: got "));
    //   Serial.print(ipStr);
    //   Serial.print(F(", expected "));
    //   Serial.println(serverBaseUrl);
    //   return false;
    // }
  }

  return true;
}

static bool requireAuth() {
  if (!hasValidToken()) {
    String json = F("{\"status\":\"error\",\"error\":\"Unauthorized\",\"message\":\"Missing or invalid token\"}");
    sendJson(401, json);
    return false;
  }
  return true;
}

static void sendNotAvailable() {
  String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Setup mode is not active\"}");
  sendJson(403, json);
}

uint8_t wifiSignalPercent() {
  if (WiFi.status() != WL_CONNECTED) return 0;

  int rssi = WiFi.RSSI(); // обычно от -100 до -40 dBm

  if (rssi <= -100) return 0;
  if (rssi >= -50)  return 100;

  // линейная аппроксимация
  return (uint8_t)(2 * (rssi + 100));
}

// ---------------------- handlers ----------------------
static void handleAbout() {
  IPAddress ip = WiFi.localIP();

  if (deviceMode == DEVICE_MODE_SETUP) {
    serverBaseUrl = "0.0.0.0";
  }

  String json = "{";
  json += "\"device_name\":\""      + deviceName + "\",";
  json += "\"device_type\":\""      + String(DEVICE_TYPE) + "\",";
#ifdef ARDUINO_ARCH_ESP8266
  json += "\"platform\":\"esp8266\",";
#elif defined(ARDUINO_ARCH_ESP32)
  json += "\"platform\":\"esp32\",";
#endif
  json += "\"firmware_version\":\"" + String(FW_VERSION) + "\",";
  json += "\"core_version\":\""     + String(CORE_VERSION) + "\",";
  json += "\"device_id\":\""        + getUniqueID() + "\",";
  json += "\"server\":\""           + serverBaseUrl + "\",";
  json += "\"status\":\""           + deviceModeToString(deviceMode) + "\",";
  json += "\"ip_address\":\""       + ip.toString() + "\",";
  json += "\"mac_address\":\""      + getMAC() + "\",";
  json += "\"wifi_signal\":"        + String(wifiSignalPercent()) + ",";
  json += "\"uptime\":"             + String(millis() / 1000);

  appendAboutJsonFields(json);

  json += "}";
  sendJson(200, json);
}

static void handleStatus() {
  if (deviceMode == DEVICE_MODE_NORMAL) {
    if (!requireAuth()) return;
  }

  String json = "{";
  json += "\"status\":\"ok\"";
  appendStatusJsonFields(json);
  json += "}";

  sendJson(200, json);
}

static void handleSetDeviceName() {
  if (deviceMode != DEVICE_MODE_NORMAL) {
    String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"Not in normal mode\"}");
    sendJson(400, json);
    return;
  }

  if (!requireAuth()) return;

  String body = server.arg("plain");
  body.trim();

  String newName = extractJsonStringValue(body, F("device_name"));
  newName.trim();

  if (newName.length() == 0) {
    String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"device_name is required\"}");
    sendJson(400, json);
    return;
  }

  String sanitized;
  for (uint16_t i = 0; i < newName.length() && sanitized.length() < DEVICE_NAME_MAX_LEN - 1; i++) {
    char c = newName[i];
    if (c >= 64 && c != '\"') sanitized += c;
  }

  if (sanitized.length() == 0) {
    String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"Invalid device_name\"}");
    sendJson(400, json);
    return;
  }

  deviceName = sanitized;
  saveDeviceConfig();

  String json = F("{\"status\":\"ok\",\"message\":\"Device name updated\"}");
  sendJson(200, json);
}

static void handleAction() {
  if (deviceMode != DEVICE_MODE_NORMAL) {
    String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Action not available in this mode\"}");
    sendJson(403, json);
    return;
  }

  if (!requireAuth()) return;

  String body = server.arg("plain");
  body.trim();

  String action = extractJsonStringValue(body, F("action"));
  if (action.length() == 0) {
    String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"Action is required\"}");
    sendJson(400, json);
    return;
  }

  String paramsJson = extractJsonObject(body, F("params"));
  if (paramsJson.length() == 0) paramsJson = body;

  String errorCode;
  String errorMessage;

  bool ok = deviceHandleAction(action, paramsJson, errorCode, errorMessage);
  if (!ok) {
    if (errorCode.length() == 0)     errorCode = "IllegalActionOrParams";
    if (errorMessage.length() == 0)  errorMessage = "Device action failed";

    String json = "{\"status\":\"error\",\"error\":\"" + errorCode +
                  "\",\"message\":\"" + errorMessage + "\"}";
    sendJson(400, json);
    return;
  }

  String json = F("{\"status\":\"ok\",\"message\":\"Action executed\"}");
  sendJson(200, json);
}

static void handleSetToken() {
  String body = server.arg("plain");
  body.trim();

  String newToken = extractJsonStringValue(body, F("token"));
  if (newToken.length() == 0) {
    String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"Token is required\"}");
    sendJson(400, json);
    return;
  }

  if (deviceMode == DEVICE_MODE_SETUP) {
    IPAddress remote = server.client().remoteIP();
    serverBaseUrl    = remote.toString();

    authToken  = newToken;
    deviceMode = DEVICE_MODE_NORMAL;
    saveDeviceConfig();

    String json = F("{\"status\":\"ok\",\"message\":\"Token set. Device mode: normal\"}");
    sendJson(200, json);
    return;
  }

  if (deviceMode == DEVICE_MODE_NORMAL) {
    if (!requireAuth()) return;

    authToken = newToken;
    saveDeviceConfig();

    String json = F("{\"status\":\"ok\",\"message\":\"Token updated\"}");
    sendJson(200, json);
    return;
  }

  String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Cannot set token in current mode\"}");
  sendJson(403, json);
}

static void handleRebootApi() {
  if (deviceMode != DEVICE_MODE_NORMAL) {
    String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Reboot available only in normal mode\"}");
    sendJson(403, json);
    return;
  }

  if (!requireAuth()) return;

  String json = F("{\"status\":\"ok\",\"message\":\"Device will reboot now\"}");
  sendJson(200, json);

  Serial.println(F("Reboot requested via REST API"));
  delay(500);
  ESP.restart();
}

static void handleResetApi() {
  if (deviceMode != DEVICE_MODE_NORMAL) {
    String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Reset available only in normal mode\"}");
    sendJson(403, json);
    return;
  }

  // TODO: Вернуть проверку авторизации!
  //if (!requireAuth()) return;

  savedSSID = "";
  savedPASS = "";
  saveWiFiConfig(savedSSID, savedPASS);

  authToken = "";
  serverBaseUrl = "";
  deviceMode = DEVICE_MODE_SETUP;
  saveDeviceConfig();

  String json = F("{\"status\":\"ok\",\"message\":\"Device reset to factory settings. Entering setup mode.\"}");
  sendJson(200, json);

  delay(800);
  ESP.restart();
}

static void handleSetChannelsSchema() {
  // setup: без токена
  // normal: только с токеном
  if (deviceMode == DEVICE_MODE_NORMAL) {
    if (!requireAuth()) return;
  } else if (deviceMode != DEVICE_MODE_SETUP) {
    String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Cannot set schema in current mode\"}");
    sendJson(403, json);
    return;
  }

  String body = server.arg("plain");
  body.trim();

  String arr = extractJsonArray(body, F("schema"));
  if (arr.length() == 0) {
    String json = F("{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"schema array is required\"}");
    sendJson(400, json);
    return;
  }

  uint8_t tmp[CHANNEL_SCHEMA_DATA_LEN];
  String err;
  if (!parseByteArray32(arr, tmp, err)) {
    String json = "{\"status\":\"error\",\"error\":\"IllegalActionOrParams\",\"message\":\"" + err + "\"}";
    sendJson(400, json);
    return;
  }

  for (uint8_t i = 0; i < CHANNEL_SCHEMA_DATA_LEN; i++) {
    channels_schema[i] = (char)tmp[i];
  }

  write_channels_schema();

  String json = F("{\"status\":\"ok\",\"message\":\"Channels schema updated\"}");
  sendJson(200, json);

  delay(200);
  ESP.restart();
}

static void handleGetChannelsSchema() {
  // Доступ
  if (deviceMode == DEVICE_MODE_NORMAL) {
    if (!requireAuth()) return;
  } else if (deviceMode != DEVICE_MODE_SETUP) {
    String json = F("{\"status\":\"error\",\"error\":\"NotAvailable\",\"message\":\"Cannot access schema in current mode\"}");
    sendJson(403, json);
    return;
  }

  // Убедимся, что рабочий буфер актуален
  load_channels_schema();

  String json = "{";
  json += "\"status\":\"ok\",";
  json += "\"schema\":[";

  for (uint8_t i = 0; i < CHANNEL_SCHEMA_DATA_LEN; i++) {
    json += String((uint8_t)channels_schema[i]);
    if (i < CHANNEL_SCHEMA_DATA_LEN - 1) json += ",";
  }

  json += "]";
  json += "}";

  sendJson(200, json);
}



// ---------------------- routes register ----------------------
void registerRestApiRoutes() {
  server.on(F("/about"),     HTTP_GET,  handleAbout);
  server.on(F("/status"),    HTTP_GET,  handleStatus);
  server.on(F("/action"),    HTTP_POST, handleAction);
  server.on(F("/set_token"), HTTP_POST, handleSetToken);
  server.on(F("/set_device_name"), HTTP_POST, handleSetDeviceName);
  server.on(F("/set_channels_schema"), HTTP_POST, handleSetChannelsSchema);
  server.on(F("/channels_schema"), HTTP_GET, handleGetChannelsSchema);

  server.on(F("/reboot"),    HTTP_POST, handleRebootApi);
  server.on(F("/reset"),     HTTP_POST, handleResetApi);
}
