#include <WiFi.h>
#include <WiFiManager.h>
#include <Firebase_ESP_Client.h>
#include <addons/TokenHelper.h>
#include <addons/RTDBHelper.h>
#include <DHT.h>
#include <time.h>
#include <HTTPClient.h>
#include <UrlEncode.h>   // library "ESP32 URL Encode" by Alessandro Pezzato

// ── KONFIGURASI HOTSPOT SETUP ──
#define AP_SSID "Naga Terbang"
#define AP_PASS "naga1234"

// ── KONFIGURASI FIREBASE ──
#define FIREBASE_HOST "monitoring-tanaman-d2cd2-default-rtdb.firebaseio.com"
#define FIREBASE_AUTH "nl635pPMhf4y5tUlHRvEzBbG14oLyrD3m3vymjKt"

// ── PIN PERANGKAT ──
#define DHT_PIN   4
#define DHT_TYPE  DHT22
#define SOIL_PIN  32
#define RELAY_PIN 26
#define FAN_PIN   27   // ← pin relay fan, sesuaikan jika berbeda

// ── KALIBRASI SOIL MOISTURE ──
#define SOIL_DRY 4095
#define SOIL_WET 0

// ── KALIBRASI DHT22 ──
#define OFFSET_SUHU       -1.1
#define OFFSET_KELEMBAPAN -10.4

// ── INTERVAL ──
#define INTERVAL_SENSOR   60000
#define INTERVAL_HISTORY  60000

// ══════════════════════════════════════════════
// ── KONFIGURASI PENYIRAMAN ──
// ══════════════════════════════════════════════
#define SOIL_MULAI_SIRAM  55
#define SOIL_STOP_SIRAM   65
#define DURASI_SIRAM_MS   (3 * 60 * 1000)
#define MAX_SIRAM_SENSOR  2
#define JAM_RESET         6
#define WINDOW_SENSOR_MULAI   10
#define WINDOW_SENSOR_SELESAI 13

// ── KONFIGURASI NOTIFIKASI WHATSAPP (Callmebot) ──
// 1. Save +34 644 44 74 99 di kontak WhatsApp
// 2. Kirim pesan: I allow callmebot to send me messages
// 3. Isi nomor HP dan API key yang diterima
#define WA_PHONE    "628xxxxxxxxx"   // nomor HP kamu format internasional tanpa +
#define WA_APIKEY   "xxxxxxx"        // API key dari Callmebot

// ── KONFIGURASI FAN (DHT22 sekarang deteksi suhu alat) ──
#define BATAS_SUHU_FAN_ON   28.0   // fan ON jika suhu >= 28°C
#define BATAS_SUHU_FAN_OFF  26.0   // fan OFF jika suhu <= 26°C

const int jadwalTetap[] = {7, 8, 9, 14, 16};
const int TOTAL_JADWAL  = sizeof(jadwalTetap) / sizeof(jadwalTetap[0]);

// ══════════════════════════════════════════════
DHT dht(DHT_PIN, DHT_TYPE);
FirebaseData fbdo;
FirebaseData fbdoStream;
FirebaseData fbdoStreamFan;
FirebaseAuth auth;
FirebaseConfig config;

// ── STATE PENYIRAMAN ──
bool          pompaNyala               = false;
unsigned long pompaMulai               = 0;
bool          jadwalSudahDieksekusi[5] = {false};
int           jamTerakhirReset         = -1;
int           jumlahSiramSensor        = 0;
int           jumlahSiramJadwal        = 0;

// ── STATE FAN ──
bool fanNyala       = false;
// -1 = AUTO, 0 = MANUAL OFF, 1 = MANUAL ON
int  fanOverride    = -1;

// ── FLAG ANTI RACE CONDITION ──
bool sedangMenyalakan = false;

// ── STATE OVERRIDE POMPA ──
int manualOverride = -1;

// ── INTERVAL ──
unsigned long lastSensor  = 0;
unsigned long lastHistory = 0;

// ── ANTI SPAM NOTIFIKASI ──
bool  notifSuhuTerkirim = false;   // agar tidak kirim berulang
unsigned long lastNotifWA = 0;
#define INTERVAL_NOTIF_WA  300000  // minimal 5 menit antar notif

// ══════════════════════════════════════════════
// KIRIM NOTIFIKASI WHATSAPP via Callmebot
// ══════════════════════════════════════════════
void kirimNotifWA(String pesan) {
  if (WiFi.status() != WL_CONNECTED) return;
  String url = "https://api.callmebot.com/whatsapp.php?phone=" + String(WA_PHONE)
             + "&apikey=" + String(WA_APIKEY)
             + "&text=" + urlEncode(pesan);
  HTTPClient http;
  http.begin(url);
  int code = http.GET();
  Serial.printf("📲 [WA] HTTP %d — %s\n", code, pesan.c_str());
  http.end();
}

// ══════════════════════════════════════════════
// FUNGSI RELAY POMPA
// ══════════════════════════════════════════════
void nyalakanPompaJadwal(int idx) {
  if (pompaNyala || sedangMenyalakan) return;
  sedangMenyalakan = true;
  digitalWrite(RELAY_PIN, LOW);
  pompaNyala    = true;
  pompaMulai    = millis();
  jumlahSiramJadwal++;
  jadwalSudahDieksekusi[idx] = true;
  sedangMenyalakan = false;
  Serial.printf("💧 [POMPA] ON — JADWAL %02d:00 | %d/%d\n",
                jadwalTetap[idx], jumlahSiramJadwal, TOTAL_JADWAL);
}

void nyalakanPompaSensor() {
  if (pompaNyala || sedangMenyalakan) return;
  if (jumlahSiramSensor >= MAX_SIRAM_SENSOR) {
    Serial.printf("🚫 [SENSOR] Ditolak — %d/%d\n", jumlahSiramSensor, MAX_SIRAM_SENSOR);
    return;
  }
  sedangMenyalakan = true;
  jumlahSiramSensor++;
  digitalWrite(RELAY_PIN, LOW);
  pompaNyala    = true;
  pompaMulai    = millis();
  sedangMenyalakan = false;
  Serial.printf("💧 [POMPA] ON — SENSOR %d/%d\n", jumlahSiramSensor, MAX_SIRAM_SENSOR);
}

void matikanPompa() {
  if (!pompaNyala) return;
  digitalWrite(RELAY_PIN, HIGH);
  pompaNyala = false;
  Serial.println("🔴 [POMPA] OFF");
}

// ══════════════════════════════════════════════
// FUNGSI FAN
// ══════════════════════════════════════════════
void nyalakanFan() {
  if (fanNyala) return;
  digitalWrite(FAN_PIN, LOW);   // active LOW relay
  fanNyala = true;
  Firebase.RTDB.setInt(&fbdo, "/fan", 1);
  Serial.println("🌀 [FAN] ON");
}

void matikanFan() {
  if (!fanNyala) return;
  digitalWrite(FAN_PIN, HIGH);
  fanNyala = false;
  Firebase.RTDB.setInt(&fbdo, "/fan", 0);
  Serial.println("🔵 [FAN] OFF");
}

// ══════════════════════════════════════════════
// STREAM CALLBACK POMPA (/relay)
// ══════════════════════════════════════════════
void streamCallback(FirebaseStream data) {
  Serial.println("🔔 [RELAY] Stream — type: " + data.dataType());
  int val = -1;
  if      (data.dataType() == "int")     val = data.intData();
  else if (data.dataType() == "boolean") val = data.boolData() ? 1 : 0;
  else if (data.dataType() == "number")  val = (int)data.floatData();

  if (val == 1) {
    manualOverride = 1;
    if (!pompaNyala && !sedangMenyalakan) {
      sedangMenyalakan = true;
      digitalWrite(RELAY_PIN, LOW);
      pompaNyala = true; pompaMulai = millis();
      sedangMenyalakan = false;
      Serial.println("💧 [RELAY] ON — MANUAL");
    }
  } else if (val == 0) {
    manualOverride = 0;
    if (pompaNyala) { digitalWrite(RELAY_PIN, HIGH); pompaNyala = false; Serial.println("🔴 [RELAY] OFF — MANUAL"); }
  } else {
    manualOverride = -1;
    Serial.println("🤖 [RELAY] Mode AUTO");
  }
}

// ══════════════════════════════════════════════
// STREAM CALLBACK FAN (/fan)
// ══════════════════════════════════════════════
void fanStreamCallback(FirebaseStream data) {
  Serial.println("🔔 [FAN] Stream — type: " + data.dataType());
  int val = -1;
  if      (data.dataType() == "int")     val = data.intData();
  else if (data.dataType() == "boolean") val = data.boolData() ? 1 : 0;
  else if (data.dataType() == "number")  val = (int)data.floatData();

  Serial.printf("🔔 [FAN] Nilai dari Firebase: %d\n", val);

  if (val == 1) {
    fanOverride = 1;
    if (!fanNyala) { digitalWrite(FAN_PIN, LOW); fanNyala = true; Serial.println("🌀 [FAN] ON — MANUAL"); }
  } else if (val == 0) {
    fanOverride = 0;
    if (fanNyala) { digitalWrite(FAN_PIN, HIGH); fanNyala = false; Serial.println("🔵 [FAN] OFF — MANUAL"); }
  } else {
    fanOverride = -1;
    Serial.println("🤖 [FAN] Mode AUTO");
  }
}

void streamTimeoutCallback(bool timeout) {
  if (timeout) Serial.println("⚠️ Stream timeout, retrying...");
}

// ══════════════════════════════════════════════
// FUNGSI BANTU
// ══════════════════════════════════════════════
String getTimestamp() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) return "n/a";
  char buf[25];
  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
  return String(buf);
}

bool dalamWindowSensor(int jam) {
  return (jam >= WINDOW_SENSOR_MULAI && jam < WINDOW_SENSOR_SELESAI);
}

// ══════════════════════════════════════════════
// SETUP
// ══════════════════════════════════════════════
void setup() {
  Serial.begin(115200);
  dht.begin();

  pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, HIGH);
  pinMode(FAN_PIN,   OUTPUT); digitalWrite(FAN_PIN,   HIGH);

  WiFiManager wifiManager;
  wifiManager.setConnectTimeout(20);
  wifiManager.setConfigPortalTimeout(120);
  if (!wifiManager.autoConnect(AP_SSID, AP_PASS)) {
    Serial.println("⚠️ WiFi gagal. Restart..."); delay(3000); ESP.restart();
  }
  Serial.println("✅ WiFi: " + WiFi.localIP().toString());

  config.database_url               = FIREBASE_HOST;
  config.signer.tokens.legacy_token = FIREBASE_AUTH;
  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);

  // ── Sync /relay saat startup ──
  if (Firebase.RTDB.getInt(&fbdo, "/relay")) {
    int v = fbdo.intData();
    if (v == 1) { manualOverride=1; digitalWrite(RELAY_PIN,LOW); pompaNyala=true; pompaMulai=millis(); Serial.println("💧 [STARTUP] Pompa ON"); }
    else if (v == 0) { manualOverride=0; Serial.println("🔴 [STARTUP] Pompa OFF"); }
    else { manualOverride=-1; Serial.println("🤖 [STARTUP] Pompa AUTO"); }
  }

  // ── Sync /fan saat startup ──
  if (Firebase.RTDB.getInt(&fbdo, "/fan")) {
    int v = fbdo.intData();
    if (v == 1) { fanOverride=1; digitalWrite(FAN_PIN,LOW); fanNyala=true; Serial.println("🌀 [STARTUP] Fan ON"); }
    else if (v == 0) { fanOverride=0; Serial.println("🔵 [STARTUP] Fan OFF"); }
    else { fanOverride=-1; Serial.println("🤖 [STARTUP] Fan AUTO"); }
  }

  // ── Stream /relay ──
  if (!Firebase.RTDB.beginStream(&fbdoStream, "/relay"))
    Serial.println("Stream relay error: " + fbdoStream.errorReason());
  Firebase.RTDB.setStreamCallback(&fbdoStream, streamCallback, streamTimeoutCallback);

  // ── Stream /fan ──
  if (!Firebase.RTDB.beginStream(&fbdoStreamFan, "/fan"))
    Serial.println("Stream fan error: " + fbdoStreamFan.errorReason());
  Firebase.RTDB.setStreamCallback(&fbdoStreamFan, fanStreamCallback, streamTimeoutCallback);

  configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov", "id.pool.ntp.org");
  Serial.print("⏰ NTP sync");
  struct tm timeinfo; int retry = 0;
  while (!getLocalTime(&timeinfo) && retry < 20) { delay(500); Serial.print("."); retry++; }
  Serial.println(getLocalTime(&timeinfo) ? "\n✅ NTP OK: " + getTimestamp() : "\n⚠️ NTP GAGAL");

  Serial.println("══════════════════════════════");
  Serial.printf("🌀 Fan ON  jika suhu >= %.1f°C\n", BATAS_SUHU_FAN_ON);
  Serial.printf("🌀 Fan OFF jika suhu <= %.1f°C\n", BATAS_SUHU_FAN_OFF);
  Serial.printf("💧 Soil ON : <= %d%% | OFF : >= %d%%\n", SOIL_MULAI_SIRAM, SOIL_STOP_SIRAM);
  Serial.println("══════════════════════════════");
}

// ══════════════════════════════════════════════
// LOOP
// ══════════════════════════════════════════════
void loop() {
  unsigned long now = millis();

  // Safety: matikan pompa jika melebihi durasi (AUTO)
  if (pompaNyala && manualOverride == -1 && (now - pompaMulai >= DURASI_SIRAM_MS)) {
    Serial.println("⏱️ [POMPA] Timeout — matikan");
    matikanPompa();
  }

  if (now - lastSensor >= INTERVAL_SENSOR) {
    lastSensor = now;

    // ── Baca Sensor ──
    float tRaw  = dht.readTemperature();
    float hRaw  = dht.readHumidity();
    int soilRaw = analogRead(SOIL_PIN);
    int soil    = constrain(map(soilRaw, SOIL_DRY, SOIL_WET, 0, 100), 0, 100);

    if (isnan(tRaw) || isnan(hRaw)) Serial.println("⚠️ DHT22 error!");
    float t = isnan(tRaw) ? 0 : constrain(tRaw + OFFSET_SUHU,       -40.0, 80.0);
    float h = isnan(hRaw) ? 0 : constrain(hRaw + OFFSET_KELEMBAPAN,   0.0, 100.0);

    // ── Waktu ──
    struct tm tm;
    bool ntpOk = getLocalTime(&tm);
    int jam    = ntpOk ? tm.tm_hour : -1;
    int menit  = ntpOk ? tm.tm_min  :  0;

    // ══ LOGIKA POMPA ══
    if (manualOverride == 1) {
      if (!pompaNyala) { digitalWrite(RELAY_PIN, LOW); pompaNyala = true; }
    } else if (manualOverride == 0) {
      if (pompaNyala) matikanPompa();
    } else {
      if (ntpOk && jam == JAM_RESET && menit == 0 && jamTerakhirReset != tm.tm_yday) {
        jamTerakhirReset = tm.tm_yday; jumlahSiramSensor = 0; jumlahSiramJadwal = 0;
        for (int i = 0; i < TOTAL_JADWAL; i++) jadwalSudahDieksekusi[i] = false;
        Serial.println("🔄 [RESET] Counter direset (06:00)");
      }
      if (ntpOk && !dalamWindowSensor(jam)) {
        for (int i = 0; i < TOTAL_JADWAL; i++)
          if (jam == jadwalTetap[i] && !jadwalSudahDieksekusi[i]) nyalakanPompaJadwal(i);
      }
      if (ntpOk && dalamWindowSensor(jam)) {
        if (soil <= SOIL_MULAI_SIRAM && !pompaNyala) nyalakanPompaSensor();
        if (soil >= SOIL_STOP_SIRAM  &&  pompaNyala) matikanPompa();
      }
    }

    // ══ NOTIFIKASI WHATSAPP ══
    unsigned long nowMs = millis();
    if (t >= BATAS_SUHU_FAN_ON) {
      // kirim notif hanya sekali saat kondisi berubah ATAU sudah > 5 menit
      if (!notifSuhuTerkirim || (nowMs - lastNotifWA >= INTERVAL_NOTIF_WA)) {
        String pesan = "⚠️ *HIDROGENZ FARM ALERT*\n"
                       "🌡️ Suhu box perangkat terlalu panas!\n"
                       "Suhu saat ini: " + String(t, 1) + "°C\n"
                       "Batas normal : < " + String(BATAS_SUHU_FAN_ON, 0) + "°C\n"
                       "Fan otomatis : " + String(fanNyala ? "ON ✅" : "OFF ❌") + "\n"
                       "Waktu        : " + getTimestamp();
        kirimNotifWA(pesan);
        notifSuhuTerkirim = true;
        lastNotifWA = nowMs;
      }
    } else if (t <= BATAS_SUHU_FAN_OFF) {
      // reset flag saat suhu sudah normal kembali
      if (notifSuhuTerkirim) {
        notifSuhuTerkirim = false;
        Serial.println("✅ [WA] Suhu normal kembali — flag reset");
      }
    }

    // ══ LOGIKA FAN ══    if (fanOverride == 1) {
      // MANUAL ON — pastikan tetap nyala
      if (!fanNyala) { digitalWrite(FAN_PIN, LOW); fanNyala = true; }

    } else if (fanOverride == 0) {
      // MANUAL OFF — pastikan tetap mati
      if (fanNyala) { digitalWrite(FAN_PIN, HIGH); fanNyala = false; }

    } else {
      // AUTO: ON jika suhu >= SUHU_FAN_ON, OFF jika suhu <= SUHU_FAN_OFF (hysteresis)
      if (t >= BATAS_SUHU_FAN_ON && !fanNyala) {
        nyalakanFan();
      } else if (t <= BATAS_SUHU_FAN_OFF && fanNyala) {
        matikanFan();
      }
    }

    // ── Log Serial ──
    String ts = getTimestamp();
    Serial.printf("✅ T:%.1f°C H:%.1f%% Soil:%d%% Pompa:%s Fan:%s Mode-P:%s Mode-F:%s ⏰%s\n",
      t, h, soil,
      pompaNyala ? "ON" : "OFF",
      fanNyala   ? "ON" : "OFF",
      manualOverride== 1?"MAN-ON": manualOverride==0?"MAN-OFF":"AUTO",
      fanOverride   == 1?"MAN-ON": fanOverride   ==0?"MAN-OFF":"AUTO",
      ts.c_str());

    // ── Firebase latest ──
    if (Firebase.ready()) {
      FirebaseJson jsonLatest;
      jsonLatest.set("suhu",         t);
      jsonLatest.set("kelembapan",   h);
      jsonLatest.set("soil",         soil);
      jsonLatest.set("pompa",        pompaNyala ? "ON" : "OFF");
      jsonLatest.set("fan",          fanNyala   ? "ON" : "OFF");
      jsonLatest.set("siram_jadwal", jumlahSiramJadwal);
      jsonLatest.set("siram_sensor", jumlahSiramSensor);
      jsonLatest.set("updated_at",   ts);
      Firebase.RTDB.setJSON(&fbdo, "/sensor/latest", &jsonLatest);
    }

    // ── Firebase history ──
    if (Firebase.ready() && (now - lastHistory >= INTERVAL_HISTORY)) {
      lastHistory = now;
      FirebaseJson jsonHistory;
      jsonHistory.set("suhu",       t);
      jsonHistory.set("kelembapan", h);
      jsonHistory.set("soil",       soil);
      jsonHistory.set("pompa",      pompaNyala ? "ON" : "OFF");
      jsonHistory.set("fan",        fanNyala   ? "ON" : "OFF");
      jsonHistory.set("updated_at", ts);
      jsonHistory.set("created_at", ts);
      Firebase.RTDB.pushJSON(&fbdo, "/sensor/history", &jsonHistory);
      Serial.println("📦 History saved: " + ts);
    }
  }
}
