#include <WiFi.h>
#include <Firebase_ESP_Client.h>
#include <esp_task_wdt.h>

// ================= WIFI =================
#define WIFI_SSID "Angga"
#define WIFI_PASSWORD "upinipinupin"

// ================= FIREBASE =================
#define DATABASE_URL "https://tomat2-default-rtdb.asia-southeast1.firebasedatabase.app"
#define DATABASE_SECRET "EsSGEME2IMqmVvWx2VZLkLSKAnJ4Meg6AblUOyxO"

FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

// ================= PIN =================
#define SOIL_PIN 34
#define PIR_PIN 27
#define RELAY_PIN 26
#define BUZZER_PIN 25

// ================= KALIBRASI =================
#define SOIL_DRY 2400
#define SOIL_WET 1100

// ================= GLOBAL =================
volatile bool pirFlag = false;
volatile unsigned long lastInterrupt = 0;

int pirState = 0;
unsigned long pirHoldTime = 0;
const int pirActiveDuration = 3000;

int lastMoist = -1;
int lastPir = -1;
int lastPump = -1;

int lastPumpState = 0;

String mode = "auto";
int pumpManual = 0;

int minMoist = 60;
int maxMoist = 80;

unsigned long lastSend = 0;
unsigned long lastRead = 0;

const int sendInterval = 10000;
const int readInterval = 7000;

// ================= OFFLINE =================
bool offlineMode = false;

unsigned long lastOfflineCheck = 0;
const int offlineCheckInterval = 10000;

// ================= BUZZER =================
unsigned long lastBuzz = 0;
unsigned long buzzerStart = 0;

bool buzzerActive = false;

const int buzzCooldown = 5000;
const int buzzerDuration = 2000;

bool sendPirNow = false;

// ================= ISR =================
void IRAM_ATTR pirISR() {

  if (millis() - lastInterrupt > 200) {

    pirFlag = true;
    lastInterrupt = millis();
  }
}

// ================= WIFI =================
void connectWiFi() {

  static unsigned long lastAttempt = 0;

  // sudah connect
  if (WiFi.status() == WL_CONNECTED) {
    return;
  }

  // retry tiap 10 detik
  if (millis() - lastAttempt < 10000) {
    return;
  }

  lastAttempt = millis();

  Serial.println("Connecting WiFi...");

  WiFi.disconnect();

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

// ================= OFFLINE CHECK =================
void checkOfflineMode() {

  // cek tiap 10 detik
  if (millis() - lastOfflineCheck < offlineCheckInterval) {
    return;
  }

  lastOfflineCheck = millis();

  // ===== WIFI PUTUS =====
  if (WiFi.status() != WL_CONNECTED) {

    if (!offlineMode) {
      Serial.println("⚠️ OFFLINE MODE");
    }

    offlineMode = true;
    return;
  }

  // ===== WIFI NORMAL =====
  if (offlineMode) {
    Serial.println("✅ BACK ONLINE");
  }

  offlineMode = false;
}

// ================= SAFE SEND =================
bool sendData(FirebaseJson &json) {

  // jangan kirim saat offline
  if (
    offlineMode ||
    WiFi.status() != WL_CONNECTED
  ) {
    return false;
  }

  // firebase belum ready
  if (!Firebase.ready()) {
    return false;
  }

  // kirim 1x saja
  return Firebase.RTDB.setJSON(
    &fbdo,
    "/sensor",
    &json
  );
}

// ================= SETUP =================
void setup() {

  Serial.begin(115200);

  pinMode(PIR_PIN, INPUT);

  pinMode(RELAY_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);

  // relay OFF
  digitalWrite(RELAY_PIN, HIGH);

  digitalWrite(BUZZER_PIN, LOW);

  analogSetAttenuation(ADC_11db);

  attachInterrupt(
    digitalPinToInterrupt(PIR_PIN),
    pirISR,
    RISING
  );

  // ===== WIFI =====
  WiFi.mode(WIFI_STA);
  WiFi.setSleep(false);

  connectWiFi();

  WiFi.setAutoReconnect(true);
  WiFi.persistent(true);

  // ===== FIREBASE =====
  config.database_url = DATABASE_URL;

  config.signer.tokens.legacy_token =
    DATABASE_SECRET;

  Firebase.begin(&config, &auth);

  Firebase.reconnectWiFi(true);

  fbdo.setBSSLBufferSize(2048, 512);

  config.timeout.serverResponse = 3000;

  // ===== WATCHDOG =====
  esp_task_wdt_config_t wdt_config = {
    .timeout_ms = 10000,
    .idle_core_mask = (1 << portNUM_PROCESSORS) - 1,
    .trigger_panic = true
  };

  esp_task_wdt_init(&wdt_config);

  esp_task_wdt_add(NULL);

  Serial.println("System Ready");
}

// ================= LOOP =================
void loop() {

  esp_task_wdt_reset();

  // ===== CHECK ONLINE/OFFLINE =====
  checkOfflineMode();

  // ===== RECONNECT WIFI =====
  connectWiFi();

  // ================= PIR =================
  if (pirFlag) {

    pirFlag = false;

    Serial.println("⚠️ GERAKAN!");

    pirState = 1;

    pirHoldTime = millis();

    sendPirNow = true;

    // ===== BUZZER =====
    if (millis() - lastBuzz > buzzCooldown) {

      lastBuzz = millis();

      buzzerStart = millis();

      buzzerActive = true;
    }
  }

  // ================= BUZZER =================
  if (buzzerActive) {

    static unsigned long lastToggle = 0;
    static bool state = false;

    if (millis() - lastToggle > 100) {

      lastToggle = millis();

      state = !state;

      digitalWrite(BUZZER_PIN, state);
    }

    // ===== STOP BUZZER =====
    if (millis() - buzzerStart > buzzerDuration) {

      buzzerActive = false;

      digitalWrite(BUZZER_PIN, LOW);
    }
  }

  // ================= RESET PIR =================
  if (
    pirState == 1 &&
    millis() - pirHoldTime > pirActiveDuration
  ) {

    pirState = 0;
  }

  // ================= SENSOR =================
  int soilRaw = analogRead(SOIL_PIN);

  int moisture = map(
    soilRaw,
    SOIL_DRY,
    SOIL_WET,
    0,
    100
  );

  moisture = constrain(moisture, 0, 100);

  // ==================================================
  // ================= LOGIKA POMPA ===================
  // ==================================================

  int pumpState = lastPumpState;

  // ===== OFFLINE =====
  if (offlineMode) {

    // paksa AUTO lokal
    if (moisture < minMoist) {

      pumpState = 1;

    } else if (moisture > maxMoist) {

      pumpState = 0;
    }

  } else {

    // ===== MODE NORMAL =====
    if (mode == "manual") {

      pumpState = pumpManual;

    } else {

      if (moisture < minMoist) {

        pumpState = 1;

      } else if (moisture > maxMoist) {

        pumpState = 0;
      }
    }
  }

  lastPumpState = pumpState;

  // relay active LOW
  digitalWrite(
    RELAY_PIN,
    pumpState ? LOW : HIGH
  );

  // ================= DEBUG =================
  Serial.printf(
    "OFF:%d Moist:%d Pump:%d PIR:%d WiFi:%d\n",
    offlineMode,
    moisture,
    pumpState,
    pirState,
    WiFi.status()
  );

  // ==================================================
  // ============== FIREBASE DI BAWAH =================
  // ==================================================

  // ================= READ FIREBASE =================
  if (
    !offlineMode &&
    millis() - lastRead > readInterval
  ) {

    if (
      WiFi.status() == WL_CONNECTED &&
      Firebase.ready()
    ) {

      Firebase.RTDB.getString(
        &fbdo,
        "/control/mode"
      );

      if (fbdo.dataType() == "string") {
        mode = fbdo.stringData();
      }

      Firebase.RTDB.getInt(
        &fbdo,
        "/control/pump"
      );

      if (fbdo.dataType() == "int") {
        pumpManual = fbdo.intData();
      }

      Firebase.RTDB.getInt(
        &fbdo,
        "/config/min_moisture"
      );

      if (fbdo.dataType() == "int") {
        minMoist = fbdo.intData();
      }

      Firebase.RTDB.getInt(
        &fbdo,
        "/config/max_moisture"
      );

      if (fbdo.dataType() == "int") {
        maxMoist = fbdo.intData();
      }
    }

    lastRead = millis();
  }

  // ================= PIR REALTIME =================
  if (sendPirNow) {

    // reset flag dulu
    sendPirNow = false;

    // hanya kirim kalau online
    if (!offlineMode) {

      FirebaseJson json;

      json.set("moisture", moisture);
      json.set("pir", 1);
      json.set("pump", pumpState);

      if (sendData(json)) {
        Serial.println("🚀 PIR SENT");
      }
    }
  }

  // ================= PERIODIC SEND =================
  if (
    !offlineMode &&
    millis() - lastSend > sendInterval
  ) {

    if (
      abs(moisture - lastMoist) > 3 ||
      pirState != lastPir ||
      pumpState != lastPump
    ) {

      FirebaseJson json;

      json.set("moisture", moisture);
      json.set("pir", pirState);
      json.set("pump", pumpState);

      if (sendData(json)) {

        Serial.printf(
          "SEND → M:%d PIR:%d P:%d\n",
          moisture,
          pirState,
          pumpState
        );
      }

      lastMoist = moisture;
      lastPir = pirState;
      lastPump = pumpState;
    }

    lastSend = millis();
  }

  delay(20);
}