#include "sh_core.h"

// -------------------- Globals definitions --------------------
sh_web_server_t server(80);

String savedSSID = "";
String savedPASS = "";

DeviceMode deviceMode    = DEVICE_MODE_SETUP;
String     deviceName    = "Unknown";
String     serverBaseUrl = "0.0.0.0";
String     authToken     = "";

// -------------------- WiFi connect strategy --------------------
static const uint32_t WIFI_RETRY_INTERVAL_MS   = 8000UL;   // как часто перезапускать попытку
static const uint32_t WIFI_CONNECT_TIMEOUT_MS  = 20000UL;  // сколько ждать "подключение" в одной попытке
static const uint32_t AP_ENABLE_AFTER_MS       = 15000UL;  // через сколько мс безуспешных попыток включать AP

static bool     ap_enabled = false;
static bool     sta_started = false;
static uint32_t sta_attempt_started_at = 0;
static uint32_t sta_last_retry_at = 0;
static uint32_t sta_first_fail_at = 0;

static uint32_t sta_pause_until = 0;

char channels_schema[CHANNEL_SCHEMA_DATA_LEN] = {0};

// -------------------- core "online" event state --------------------
static bool sta_was_connected = false;
static bool online_event_sent_for_this_connection = false;

static bool sta_is_paused() {
  return (sta_pause_until != 0) && ((int32_t)(sta_pause_until - millis()) > 0);
}

static void sta_pause_for(uint32_t ms) {
  sta_pause_until = millis() + ms;
}

// -------------------- EEPROM helpers --------------------
uint16_t getDeviceEepromStart() {
  return DEVICE_EEPROM_START;
}

uint16_t getDeviceEepromSize() {
  return DEVICE_EEPROM_SIZE;
}

String readStringFromEEPROM(uint16_t addr, uint16_t maxLen) {
  char buf[100];
  if (maxLen > sizeof(buf)) maxLen = sizeof(buf);

  uint8_t first = EEPROM.read(addr);

  if (first == 0xFF || first == 0x00) {
    return String("");
  }

  buf[0] = first;

  for (uint16_t i = 1; i < maxLen; i++) {
    uint8_t b = EEPROM.read(addr + i);

    if (b == 0xFF || b == 0x00) {
      buf[i] = '\0';
      return String(buf);
    }

    buf[i] = b;
  }

  buf[maxLen - 1] = '\0';
  return String(buf);
}

void writeStringToEEPROM(uint16_t addr, const String &str, uint16_t maxLen) {
  uint16_t len = str.length();
  if (len >= maxLen) len = maxLen - 1;

  for (uint16_t i = 0; i < len; i++) {
    EEPROM.write(addr + i, str[i]);
  }
  EEPROM.write(addr + len, 0);

  for (uint16_t i = len + 1; i < maxLen; i++) {
    EEPROM.write(addr + i, 0);
  }
}

static uint8_t crc8_simple(const uint8_t* data, uint16_t len) {
  uint8_t crc = 0;
  for (uint16_t i = 0; i < len; i++) crc ^= data[i];
  return crc;
}

bool is_channels_schema_valid() {
  EEPROM.begin(EEPROM_SIZE);

  uint8_t m0 = EEPROM.read(CHANNEL_SCHEMA_META_ADDR + 0);
  uint8_t m1 = EEPROM.read(CHANNEL_SCHEMA_META_ADDR + 1);
  uint8_t ver = EEPROM.read(CHANNEL_SCHEMA_META_ADDR + 2);
  uint8_t crc_stored = EEPROM.read(CHANNEL_SCHEMA_META_ADDR + 3);

  if (m0 != CHANNEL_SCHEMA_MAGIC_0 || m1 != CHANNEL_SCHEMA_MAGIC_1) {
    EEPROM.end();
    return false;
  }
  if (ver != CHANNEL_SCHEMA_VERSION) {
    EEPROM.end();
    return false;
  }

  uint8_t tmp[CHANNEL_SCHEMA_DATA_LEN];
  for (uint16_t i = 0; i < CHANNEL_SCHEMA_DATA_LEN; i++) {
    tmp[i] = EEPROM.read(CHANNEL_SCHEMA_DATA_ADDR + i);
  }

  EEPROM.end();

  uint8_t crc_calc = crc8_simple(tmp, CHANNEL_SCHEMA_DATA_LEN);
  return (crc_calc == crc_stored);
}

void load_channels_schema() {
  EEPROM.begin(EEPROM_SIZE);

  for (uint16_t i = 0; i < CHANNEL_SCHEMA_DATA_LEN; i++) {
    channels_schema[i] = (char)EEPROM.read(CHANNEL_SCHEMA_DATA_ADDR + i);
  }

  EEPROM.end();
}

void write_channels_schema() {
  EEPROM.begin(EEPROM_SIZE);

  // data (32)
  for (uint16_t i = 0; i < CHANNEL_SCHEMA_DATA_LEN; i++) {
    EEPROM.write(CHANNEL_SCHEMA_DATA_ADDR + i, (uint8_t)channels_schema[i]);
  }

  // meta (4)
  EEPROM.write(CHANNEL_SCHEMA_META_ADDR + 0, CHANNEL_SCHEMA_MAGIC_0);
  EEPROM.write(CHANNEL_SCHEMA_META_ADDR + 1, CHANNEL_SCHEMA_MAGIC_1);
  EEPROM.write(CHANNEL_SCHEMA_META_ADDR + 2, CHANNEL_SCHEMA_VERSION);

  uint8_t crc = crc8_simple((const uint8_t*)channels_schema, CHANNEL_SCHEMA_DATA_LEN);
  EEPROM.write(CHANNEL_SCHEMA_META_ADDR + 3, crc);

  EEPROM.commit();
  EEPROM.end();
}


void saveWiFiConfig(const String &ssid, const String &pass) {
  EEPROM.begin(EEPROM_SIZE);
  writeStringToEEPROM(SSID_ADDR, ssid, SSID_MAX_LEN);
  writeStringToEEPROM(PASS_ADDR, pass, PASS_MAX_LEN);
  EEPROM.commit();
  EEPROM.end();
}

void loadWiFiConfig() {
  EEPROM.begin(EEPROM_SIZE);
  savedSSID = readStringFromEEPROM(SSID_ADDR, SSID_MAX_LEN);
  savedPASS = readStringFromEEPROM(PASS_ADDR, PASS_MAX_LEN);
  EEPROM.end();
}

// -------------------- Device config save/load --------------------
void loadDeviceConfig() {
  EEPROM.begin(EEPROM_SIZE);

  uint8_t rawMode = EEPROM.read(DEVICE_MODE_ADDR);
  String name     = readStringFromEEPROM(DEVICE_NAME_ADDR, DEVICE_NAME_MAX_LEN);
  String token    = readStringFromEEPROM(AUTH_TOKEN_ADDR, AUTH_TOKEN_MAX_LEN);
  String server_s = readStringFromEEPROM(SERVER_ADDR, SERVER_MAX_LEN);

  EEPROM.end();

  if (rawMode <= DEVICE_MODE_UPDATING) {
    deviceMode = static_cast<DeviceMode>(rawMode);
  } else {
    deviceMode = DEVICE_MODE_SETUP;
    authToken  = "";
    serverBaseUrl = "";
  }

  if (deviceMode == DEVICE_MODE_SETUP) {
    name = deviceName;
  } else {
    if (!(name.length() > 0 && name[0] > 31 && name[0] < 127)) {
      name = deviceName;
    }
  }

  if (name.length() > 0)      deviceName    = name;
  if (token.length() > 0)     authToken     = token;
  if (server_s.length() > 0)  serverBaseUrl = server_s;
}

void saveDeviceConfig() {
  EEPROM.begin(EEPROM_SIZE);

  EEPROM.write(DEVICE_MODE_ADDR, static_cast<uint8_t>(deviceMode));
  writeStringToEEPROM(DEVICE_NAME_ADDR, deviceName,     DEVICE_NAME_MAX_LEN);
  writeStringToEEPROM(AUTH_TOKEN_ADDR,  authToken,      AUTH_TOKEN_MAX_LEN);
  writeStringToEEPROM(SERVER_ADDR,      serverBaseUrl,  SERVER_MAX_LEN);

  EEPROM.commit();
  EEPROM.end();
}

static void wifi_enable_ap_if_needed() {
  if (ap_enabled) return;

  String ap_ssid = "SH-DEVICE-";
  ap_ssid += getUniqueID();

  const char* ap_pass = "noAccess";

  WiFi.mode(WIFI_AP_STA); // важно: AP+STA одновременно
  bool ok = WiFi.softAP(ap_ssid.c_str(), ap_pass);

  if (ok) {
    ap_enabled = true;
    sta_pause_for(30000); // 30 секунд
    Serial.print(F("AP started, SSID: "));
    Serial.println(ap_ssid);
    Serial.print(F("AP IP: "));
    Serial.println(WiFi.softAPIP());
  } else {
    Serial.println(F("AP start FAILED"));
  }
}

static void wifi_disable_ap_if_enabled() {
  if (!ap_enabled) return;

  WiFi.softAPdisconnect(true);
  ap_enabled = false;

  if (WiFi.status() == WL_CONNECTED) {
    WiFi.mode(WIFI_STA);
  }

  Serial.println(F("AP stopped"));
}

static void wifi_start_sta_attempt() {
  if (savedSSID.length() == 0) {
    Serial.println(F("No saved SSID, skipping STA connect"));
    return;
  }

  if (WiFi.getMode() != WIFI_STA && WiFi.getMode() != WIFI_AP_STA) {
    WiFi.mode(WIFI_STA);
  }

  Serial.print(F("STA connect start, SSID: "));
  Serial.println(savedSSID);

  WiFi.begin(savedSSID.c_str(), savedPASS.c_str());

  sta_started = true;
  sta_attempt_started_at = millis();
}

// -------------------- core HTTP helper --------------------
bool core_post_json_to_server(const String &path,
                             const String &json_body,
                             uint32_t timeout_ms,
                             int &http_code_out)
{
  http_code_out = -1;

  if (WiFi.status() != WL_CONNECTED) return false;
  if (authToken.length() == 0) return false;
  if (serverBaseUrl.length() == 0) return false;
  if (serverBaseUrl == "0.0.0.0") return false;

  String url = "http://" + serverBaseUrl + path;

  WiFiClient client;
  HTTPClient http;

  if (!http.begin(client, url)) return false;

  http.setTimeout(timeout_ms);
  http.addHeader("Content-Type", "application/json");
  http.addHeader("Authorization", "Bearer " + authToken);

  int code = http.POST(json_body);
  http.end();

  http_code_out = code;
  return (code >= 200 && code < 300);
}

// -------------------- core online event --------------------
static void core_send_online_event_once() {
  if (deviceMode != DEVICE_MODE_NORMAL) return;
  if (online_event_sent_for_this_connection) return;

  if (serverBaseUrl.length() == 0 || serverBaseUrl == "0.0.0.0") return;
  if (authToken.length() == 0) return;

  IPAddress ip = WiFi.localIP();

  String body = "{";
  body += "\"event_name\":\"online\",";
  body += "\"data\":{";
  body += "\"device_ip\":\"" + ip.toString() + "\"";
  body += "},";
  body += "\"device_id\":\"" + getUniqueID() + "\"";
  body += "}";

  int http_code = -1;
  (void)core_post_json_to_server("/events/new", body, 1500, http_code);

  online_event_sent_for_this_connection = true;
}

static void wifi_tick() {
  if (WiFi.status() != WL_CONNECTED) {
    if (sta_was_connected) {
      sta_was_connected = false;
      online_event_sent_for_this_connection = false;
    }
  }

  if (savedSSID.length() == 0) {
    wifi_enable_ap_if_needed();
    return;
  }

  if (WiFi.status() == WL_CONNECTED) {
    if (!sta_was_connected) {
      sta_was_connected = true;
      online_event_sent_for_this_connection = false;
    }

    if (sta_first_fail_at != 0) sta_first_fail_at = 0;
    wifi_disable_ap_if_enabled();

    core_send_online_event_once();
    return;
  }

  if (ap_enabled) {
    uint8_t ap_sta_num = WiFi.softAPgetStationNum();
    if (ap_sta_num > 0) {
      sta_pause_for(60000);
    }
  }

  if (sta_is_paused()) return;

  if (deviceMode == DEVICE_MODE_SETUP) {
    wifi_enable_ap_if_needed();
  }

  if (!sta_started) {
    wifi_start_sta_attempt();
    return;
  }

  uint32_t now = millis();

  if (now - sta_attempt_started_at > WIFI_CONNECT_TIMEOUT_MS) {
    if (sta_first_fail_at == 0) sta_first_fail_at = now;

    if (now - sta_last_retry_at > WIFI_RETRY_INTERVAL_MS) {
      sta_last_retry_at = now;

      Serial.println(F("STA retry..."));
      WiFi.disconnect();
      delay(50);
      wifi_start_sta_attempt();
    }
  }

  if (sta_first_fail_at != 0 && (now - sta_first_fail_at > AP_ENABLE_AFTER_MS)) {
    wifi_enable_ap_if_needed();
  }
}

// -------------------- Utils --------------------
String getUniqueID() {
  return platform_get_unique_id();
}

String getMAC() {
  return platform_get_mac_string();
}

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

  const uint8_t default_pins[4] = {14, 12, 13, 15};

  for (uint8_t ch = 0; ch < 4; ch++) {
    uint8_t base = ch * CHANNEL_BYTES;
    channels_schema[base + 0] = (char)default_pins[ch]; // pin
    channels_schema[base + 1] = (char)ch;               // indicator
    channels_schema[base + 2] = (char)SH_PIN_UNUSED;    // feedback
    channels_schema[base + 3] = (char)0;    // reserved
  }
}


uint8_t sh_channel_get_u8(uint8_t ch, ShChannelField field) {
  if (ch >= CHANNEL_MAX) return SH_PIN_UNUSED;

  uint16_t idx = (uint16_t)ch * CHANNEL_BYTES + (uint8_t)field;
  if (idx >= CHANNEL_SCHEMA_DATA_LEN) return SH_PIN_UNUSED;

  return (uint8_t)channels_schema[idx];
}

void sh_channel_set_u8(uint8_t ch, ShChannelField field, uint8_t v) {
  if (ch >= CHANNEL_MAX) return;

  uint16_t idx = (uint16_t)ch * CHANNEL_BYTES + (uint8_t)field;
  if (idx >= CHANNEL_SCHEMA_DATA_LEN) return;

  channels_schema[idx] = (char)v;
}

uint8_t sh_channel_pin(uint8_t ch) {
  return sh_channel_get_u8(ch, SH_CH_PIN);
}

uint8_t sh_channel_indicator(uint8_t ch) {
  return sh_channel_get_u8(ch, SH_CH_INDICATOR);
}

uint8_t sh_channel_feedback_pin(uint8_t ch) {
  return sh_channel_get_u8(ch, SH_CH_FEEDBACK);
}

bool sh_channel_is_inverted(uint8_t ch) {
  uint8_t flags = sh_channel_get_u8(ch, SH_CH_RESERVED);
  if (flags == SH_PIN_UNUSED) flags = 0;
  return flags == 1;
}

void sh_channel_set_inverted(uint8_t ch, bool inverted) {
  uint8_t flags = sh_channel_get_u8(ch, SH_CH_RESERVED);
  if (flags == SH_PIN_UNUSED) flags = 0;

  if (inverted) flags |= SH_CH_FLAG_INVERT;
  else          flags &= (uint8_t)~SH_CH_FLAG_INVERT;

  sh_channel_set_u8(ch, SH_CH_RESERVED, flags);
}




// -------------------- Core setup / loop --------------------
void coreSetup() {
  Serial.begin(115200);
  delay(200);

  Serial.println();
  Serial.println(F("Booting..."));
  Serial.print(F("Firmware: "));
  Serial.println(FW_VERSION);
  Serial.print(F("Core: "));
  Serial.println(CORE_VERSION);

  if (!is_channels_schema_valid()) {
    init_default_channels_schema();
    write_channels_schema();
  } else {
    load_channels_schema();
  }


  loadWiFiConfig();
  loadDeviceConfig();

  Serial.print(F("Saved SSID: "));
  Serial.println(savedSSID);

  WiFi.persistent(false);
  WiFi.setAutoReconnect(false);

  if (deviceMode == DEVICE_MODE_SETUP) {
    wifi_enable_ap_if_needed();
    wifi_start_sta_attempt();
  } else {
    WiFi.mode(WIFI_STA);
    platform_wifi_apply_radio_tuning();
    wifi_start_sta_attempt();
  }

  registerWebUiRoutes();
  registerRestApiRoutes();

  server.begin();
  Serial.println(F("HTTP server started"));
}

void coreLoop() {
  server.handleClient();
  wifi_tick();
  delay(0);
}
