/*
 * PisoLock Smart Charging Slot Controller
 * For: ESP32-C3 SuperMini
 * Version: 5.0 - Fixed (relay polarity + USB-charger compatibility)
 * 
 * Based on working ESP32 code, adapted for C3
 * USE WITH: Tools → Board → ESP32-C3 Dev Module
 *
 * v5 fixes:
 *   1. Removed `while (!Serial)` hang — ESP32-C3 now boots correctly on
 *      plain 5V USB wall chargers (no PC needed).
 *   2. Relay pin is forced to OFF level BEFORE anything else in setup(),
 *      so the relay no longer clicks ON during the 2-second boot window.
 *   3. Relay polarity is now configurable via RELAY_ACTIVE_LOW constant,
 *      so you can flip it without rewriting setRelay().
 */

#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <Preferences.h>

// WiFi config
const char* WIFI_SSID     = "ALHN-8641";
const char* WIFI_PASSWORD = "2444666668888888";
const int   WEBSERVER_PORT = 8080;

// GPIO pins for C3 (simplified - GPIO 0-7 only)
const int RELAY_PIN = 5;
const int LED_PIN = 6;

// ═══════════════════════════════════════════════════════════════════════
// RELAY POLARITY — change this if your relay is inverted
// ═══════════════════════════════════════════════════════════════════════
// Most blue single-channel relay modules with optocoupler = ACTIVE-LOW
//   → LOW trigger = relay ON, HIGH trigger = relay OFF
//   → Use: RELAY_ACTIVE_LOW = true  (default)
//
// Some bare relays / some modules without optocoupler = ACTIVE-HIGH
//   → HIGH trigger = relay ON, LOW trigger = relay OFF
//   → Use: RELAY_ACTIVE_LOW = false
//
// SYMPTOM if wrong: relay is permanently ON (click on boot, stays closed
// even when app says "OFF"), or you hear the relay click but the phone
// doesn't charge.
// FIX: flip this flag and re-upload.
// ═══════════════════════════════════════════════════════════════════════
const bool RELAY_ACTIVE_LOW = true;

// Derived levels — don't edit these directly
#define RELAY_ON_LEVEL  (RELAY_ACTIVE_LOW ? LOW  : HIGH)
#define RELAY_OFF_LEVEL (RELAY_ACTIVE_LOW ? HIGH : LOW)

// Battery settings
const int DEFAULT_MIN_THRESHOLD = 30;
const int DEFAULT_MAX_THRESHOLD = 90;

// Timing
const unsigned long UPDATE_INTERVAL_MS = 5000;
const unsigned long BATTERY_TIMEOUT_MS = 300000;

enum BatteryMode { MODE_API, MODE_SENSOR };

struct SlotConfig {
  String slot_id;
  int min_threshold;
  int max_threshold;
  BatteryMode mode;
};

SlotConfig config = {
  .slot_id = "slot1",
  .min_threshold = DEFAULT_MIN_THRESHOLD,
  .max_threshold = DEFAULT_MAX_THRESHOLD,
  .mode = MODE_API
};

int currentBattery = -1;
bool relayState = false;
bool manualOverride = false;
unsigned long lastBatteryUpdate = 0;
unsigned long lastCheckMs = 0;
unsigned long lastBlinkMs = 0;
bool ledState = false;

bool wifiWasConnected = false;
unsigned long wifiRetryMs = 0;
unsigned long wifiRetryDelay = 2000;

Preferences preferences;
WebServer server(WEBSERVER_PORT);

// ========== RELAY ==========
void setRelay(bool state) {
  relayState = state;
  digitalWrite(RELAY_PIN, state ? RELAY_ON_LEVEL : RELAY_OFF_LEVEL);
  Serial.printf("RELAY: %s (pin=%s)\n",
    state ? "ON" : "OFF",
    state ? (RELAY_ACTIVE_LOW ? "LOW" : "HIGH")
          : (RELAY_ACTIVE_LOW ? "HIGH" : "LOW"));
}

// ========== API: GET /status ==========
void handleStatusRequest() {
  DynamicJsonDocument doc(512);
  doc["slot_id"] = config.slot_id;
  doc["battery"] = currentBattery;
  doc["relay"] = relayState ? "ON" : "OFF";
  doc["min_threshold"] = config.min_threshold;
  doc["max_threshold"] = config.max_threshold;
  doc["mode"] = (config.mode == MODE_API) ? "API" : "SENSOR";
  doc["wifi_rssi"] = WiFi.RSSI();
  doc["ip"] = WiFi.localIP().toString();
  
  String json;
  serializeJson(doc, json);
  server.send(200, "application/json", json);
}

// ========== API: POST /battery ==========
void handleBatteryUpdate() {
  if (server.hasArg("plain")) {
    DynamicJsonDocument doc(256);
    DeserializationError error = deserializeJson(doc, server.arg("plain"));
    
    if (!error && doc.containsKey("battery")) {
      int battery = doc["battery"];
      if (battery >= 0 && battery <= 100) {
        currentBattery = battery;
        lastBatteryUpdate = millis();
        manualOverride = false;
        
        DynamicJsonDocument response(256);
        response["status"] = "OK";
        response["battery"] = currentBattery;
        
        String json;
        serializeJson(response, json);
        server.send(200, "application/json", json);
        
        Serial.printf("BATTERY: %d%%\n", currentBattery);
        return;
      }
    }
  }
  server.send(400, "application/json", "{\"error\":\"Invalid\"}");
}

// ========== API: POST /relay/on ==========
void handleRelayOn() {
  setRelay(true);
  manualOverride = true;
  
  DynamicJsonDocument response(256);
  response["status"] = "OK";
  response["relay"] = "ON";
  
  String json;
  serializeJson(response, json);
  server.send(200, "application/json", json);
}

// ========== API: POST /relay/off ==========
void handleRelayOff() {
  setRelay(false);
  manualOverride = true;
  
  DynamicJsonDocument response(256);
  response["status"] = "OK";
  response["relay"] = "OFF";
  
  String json;
  serializeJson(response, json);
  server.send(200, "application/json", json);
}

// ========== API: POST /set-threshold ==========
void handleSetThreshold() {
  if (server.hasArg("plain")) {
    DynamicJsonDocument doc(256);
    DeserializationError error = deserializeJson(doc, server.arg("plain"));
    
    if (!error) {
      bool changed = false;
      if (doc.containsKey("min")) {
        int minVal = doc["min"];
        if (minVal >= 0 && minVal <= 100) { config.min_threshold = minVal; changed = true; }
      }
      if (doc.containsKey("max")) {
        int maxVal = doc["max"];
        if (maxVal >= 0 && maxVal <= 100) { config.max_threshold = maxVal; changed = true; }
      }
      if (changed) {
        saveConfig();
        DynamicJsonDocument resp(128);
        resp["status"] = "OK";
        resp["min_threshold"] = config.min_threshold;
        resp["max_threshold"] = config.max_threshold;
        String json; serializeJson(resp, json);
        server.send(200, "application/json", json);
        Serial.printf("THRESHOLD: min=%d%%, max=%d%%\n", config.min_threshold, config.max_threshold);
        return;
      }
    }
  }
  server.send(400, "application/json", "{\"error\":\"Invalid\"}");
}

// ========== API: POST /config ==========
void handleConfig() {
  if (server.hasArg("plain")) {
    DynamicJsonDocument doc(256);
    DeserializationError error = deserializeJson(doc, server.arg("plain"));
    
    if (!error) {
      if (doc.containsKey("slot_id")) {
        config.slot_id = doc["slot_id"].as<String>();
      }
      if (doc.containsKey("mode")) {
        String m = doc["mode"].as<String>();
        config.mode = (m == "SENSOR") ? MODE_SENSOR : MODE_API;
      }
      saveConfig();
      DynamicJsonDocument resp(128);
      resp["status"] = "OK";
      resp["slot_id"] = config.slot_id;
      resp["mode"] = (config.mode == MODE_API) ? "API" : "SENSOR";
      String json; serializeJson(resp, json);
      server.send(200, "application/json", json);
      return;
    }
  }
  server.send(400, "application/json", "{\"error\":\"Invalid\"}");
}

// ========== SETUP ROUTES ==========
void setupRoutes() {
  server.on("/status", HTTP_GET, handleStatusRequest);
  server.on("/battery", HTTP_POST, handleBatteryUpdate);
  server.on("/relay/on", HTTP_POST, handleRelayOn);
  server.on("/relay/off", HTTP_POST, handleRelayOff);
  server.on("/set-threshold", HTTP_POST, handleSetThreshold);
  server.on("/config", HTTP_POST, handleConfig);
  server.onNotFound([]() {
    server.send(404, "text/plain", "404 Not Found");
  });
}

// ========== CHARGING LOGIC ==========
void checkAndUpdateCharging() {
  if (manualOverride || currentBattery < 0) {
    return;
  }
  
  if (lastBatteryUpdate > 0 && (millis() - lastBatteryUpdate > BATTERY_TIMEOUT_MS)) {
    setRelay(false);
    return;
  }
  
  if (currentBattery < config.min_threshold && !relayState) {
    Serial.printf("START CHARGE: %d%% < %d%%\n", currentBattery, config.min_threshold);
    setRelay(true);
  } else if (currentBattery >= config.max_threshold && relayState) {
    Serial.printf("STOP CHARGE: %d%% >= %d%%\n", currentBattery, config.max_threshold);
    setRelay(false);
  }
}

// ========== SENSOR MODE ==========
int readBatterySensor() {
  static int simBattery = 50;
  
  if (relayState) {
    simBattery += 1;
    if (simBattery >= 100) simBattery = 100;
  } else {
    if (random(0, 10) < 2) {
      simBattery -= 1;
      if (simBattery < 0) simBattery = 0;
    }
  }
  
  return simBattery;
}

// ========== STORAGE ==========
void loadConfig() {
  preferences.begin("smartcharge", false);
  config.slot_id = preferences.getString("slot_id", "slot1");
  config.min_threshold = preferences.getInt("min_threshold", DEFAULT_MIN_THRESHOLD);
  config.max_threshold = preferences.getInt("max_threshold", DEFAULT_MAX_THRESHOLD);
  config.mode = (BatteryMode)preferences.getInt("mode", MODE_API);
  preferences.end();
  
  Serial.printf("CONFIG: slot=%s, min=%d%%, max=%d%%\n",
    config.slot_id.c_str(), config.min_threshold, config.max_threshold);
}

void saveConfig() {
  preferences.begin("smartcharge", false);
  preferences.putString("slot_id", config.slot_id);
  preferences.putInt("min_threshold", config.min_threshold);
  preferences.putInt("max_threshold", config.max_threshold);
  preferences.putInt("mode", (int)config.mode);
  preferences.end();
  Serial.println("CONFIG SAVED");
}

// ========== WIFI ==========
void connectWiFi() {
  Serial.println("\nConnecting to WiFi...");
  Serial.printf("SSID: %s\n\n", WIFI_SSID);
  
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  
  int tries = 0;
  while (WiFi.status() != WL_CONNECTED && tries < 60) {
    delay(500);
    Serial.print(".");
    tries++;
  }
  
  Serial.println("\n");
  
  if (WiFi.status() == WL_CONNECTED) {
    Serial.println("✓ WiFi CONNECTED!");
    Serial.printf("IP: %s\n", WiFi.localIP().toString().c_str());
    Serial.printf("RSSI: %d dBm\n", WiFi.RSSI());
    Serial.printf("http://%s:%d/status\n\n", WiFi.localIP().toString().c_str(), WEBSERVER_PORT);
    wifiWasConnected = true;
  } else {
    Serial.println("✗ WiFi FAILED - Retrying...\n");
  }
}

void handleWiFiReconnect() {
  bool ok = (WiFi.status() == WL_CONNECTED);
  
  if (ok) {
    if (!wifiWasConnected) {
      Serial.printf("✓ Reconnected: %s\n", WiFi.localIP().toString().c_str());
    }
    wifiWasConnected = true;
    return;
  }
  
  if (wifiWasConnected) {
    Serial.println("✗ WiFi Lost");
    wifiWasConnected = false;
    wifiRetryMs = millis();
  }
  
  unsigned long now = millis();
  if (now - wifiRetryMs >= 5000) {
    WiFi.disconnect(false);
    WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
    wifiRetryMs = now;
  }
}

// ========== LED ==========
void updateLED() {
  unsigned long now = millis();
  
  if (relayState) {
    if (now - lastBlinkMs > 250) {
      ledState = !ledState;
      digitalWrite(LED_PIN, ledState ? HIGH : LOW);
      lastBlinkMs = now;
    }
  } else if (currentBattery >= 0) {
    if (now - lastBlinkMs > 1000) {
      ledState = !ledState;
      digitalWrite(LED_PIN, ledState ? HIGH : LOW);
      lastBlinkMs = now;
    }
  } else {
    if (ledState) {
      ledState = false;
      digitalWrite(LED_PIN, LOW);
    }
  }
}

// ========== SETUP ==========
void setup() {
  // CRITICAL: Set relay pin HIGH *immediately* before doing anything else,
  // so the relay stays OFF during boot instead of floating ON for ~2 s.
  // (For active-LOW relay modules; flip to LOW if yours is active-HIGH.)
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, RELAY_OFF_LEVEL);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  Serial.begin(115200);
  // DO NOT use `while (!Serial)` here — on the ESP32-C3 SuperMini powered
  // by a plain USB wall charger (no PC host), Serial never becomes "ready"
  // because USB-CDC never negotiates. That would hang setup() forever,
  // preventing WiFi connect and web server startup. Just give USB a short
  // grace period and move on whether or not a PC is listening.
  delay(1500);

  setRelay(false);  // explicitly set relay OFF state in software too

  Serial.println("\n╔═══════════════════════════════════════╗");
  Serial.println("║  PISOLOCK SMART CHARGING v5.0         ║");
  Serial.println("║  ESP32-C3 SuperMini (Fixed)           ║");
  Serial.println("╚═══════════════════════════════════════╝");

  loadConfig();

  // FIX: If SENSOR mode, read battery immediately so /status returns real data from the start
  if (config.mode == MODE_SENSOR) {
    currentBattery = readBatterySensor();
    lastBatteryUpdate = millis();
    Serial.printf("INIT SENSOR: %d%%\n", currentBattery);
  }

  connectWiFi();

  setupRoutes();
  server.begin();

  Serial.printf("WebServer: Port %d\n", WEBSERVER_PORT);
  Serial.printf("Slot ID: %s\n", config.slot_id.c_str());
  Serial.println("Status: Ready\n");
}

// ========== LOOP ==========
void loop() {
  handleWiFiReconnect();
  server.handleClient();
  
  unsigned long now = millis();
  
  if (config.mode == MODE_SENSOR) {
    if (now - lastCheckMs >= UPDATE_INTERVAL_MS) {
      currentBattery = readBatterySensor();
      lastBatteryUpdate = now;
      lastCheckMs = now;
      Serial.printf("SENSOR: %d%%\n", currentBattery);
    }
  }
  
  if (now - lastCheckMs >= UPDATE_INTERVAL_MS) {
    checkAndUpdateCharging();
    lastCheckMs = now;
  }
  
  updateLED();
  
  delay(10);
}
