#include <Arduino.h>
#include <WiFi.h>
#include <FirebaseESP32.h>
#include <WiFiManager.h>
#include <Wire.h>
#include "RTClib.h"
#include "time.h"

// ================= FIREBASE =================
#define FIREBASE_HOST "telurku-fa78c-default-rtdb.asia-southeast1.firebasedatabase.app"
#define FIREBASE_AUTH "3zofmC4xXE1g80NTQtB8X7JZp6eirMzUAwZC6iM8"

// ================= PIN =================
const int pinENA = 13;
const int pinIN1 = 12;
const int pinIN2 = 14;
const int pinIR1 = 34;
const int pinIR2 = 35; 
const int pinIR3 = 32;
const int pinSDA = 21;
const int pinSCL = 22;

// ================= OBJEK =================
RTC_DS3231 rtc;
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

// ================= VARIABEL SISTEM =================
// Counter telur dari kandang
int jumlahTelur1 = 0;
int jumlahTelur2 = 0;

// ===== CHECKPOINT UNTUK DELTA CALCULATION =====
// Ini TIDAK akan di-reset saat reset counter, hanya reset saat panen berhasil
int ir1CheckpointPanen = 0;
int ir2CheckpointPanen = 0;
int jumlahTelurMasuk = 0;
int totalTelurHarusKeBak = 0;

// Total telur yang sudah dipanen sebelumnya
int totalTelurSudahDipanen = 0;

// Target panen saat conveyor berjalan
int targetPanen = 0;

// Status sensor sebelumnya
int lastStatusIR1 = HIGH;
int lastStatusIR2 = HIGH;
int lastStatusIR3 = HIGH;

// Status sistem
bool motorBerjalan = false;
bool modePanenAktif = false;

bool dataTersinkron = false;
bool firebaseInitialized = false;

// Timer motor
unsigned long waktuMulaiMotor = 0;
unsigned long waktuMulaiFailsafe = 0;

// Durasi dari Firebase
int durasiTargetDetik = 0;

// Durasi motor yang dapat diperpanjang
unsigned long durasiMotorAkhir = 0;

// Waktu telur terakhir masuk ke bak (untuk cek durasi tanpa telur)
unsigned long waktuTelurTerakhirMasuk = 0;

// Flag untuk mencegah penambahan durasi berkali-kali
bool durasiSudahDitambahkan = false;

// Waktu ketika target telur tercapai di IR3
unsigned long waktuTargetTercapai = 0;

// Timeout setelah target tercapai sebelum motor OFF (10 detik)
const unsigned long timeoutMotorAfterTarget = 10000;

// Flag untuk motor yang sedang dalam proses "extra push" mengangkut sisa telur
bool motorSedangPushSisa = false;

// Timeout darurat 3 menit
const unsigned long timeoutFailsafe = 180000;

// ===== RESET COUNTER PENDING (event-based, bukan time-based) =====
bool resetCounterPending = false;      // Flag: diminta untuk reset setelah panen selesai

// ================= TIMER TASK =================

unsigned long lastTaskSensor = 0;
unsigned long lastTaskFirebase = 0;
unsigned long lastTaskJadwal = 0;
unsigned long lastTaskResetWiFi = 0;

unsigned long lastReconnectAttempt = 0;
unsigned long lastWiFiStatusPrint = 0;

const unsigned long intervalSensor = 50;
const unsigned long intervalFirebase = 5000;
const unsigned long intervalJadwal = 15000;
const unsigned long intervalResetWiFi = 2000;
const unsigned long intervalReconnect = 10000;

// ================= MOTOR =================

void motorON()
{
    // Hitung telur BARU yang masuk sejak panen terakhir selesai
    // Menggunakan checkpoint panen yang TIDAK di-reset dengan counter
    int telurBaruIR1 = jumlahTelur1 - ir1CheckpointPanen;
    int telurBaruIR2 = jumlahTelur2 - ir2CheckpointPanen;
    
    totalTelurHarusKeBak = telurBaruIR1 + telurBaruIR2;
    targetPanen = totalTelurHarusKeBak;

    if (targetPanen <= 0)
    {
        Serial.println(
            "[PANEN] Tidak ada telur baru"
        );
        return;
    }

    jumlahTelurMasuk = 0;
    motorSedangPushSisa = false;
    waktuTargetTercapai = 0;

    modePanenAktif = true;

    digitalWrite(pinIN1, HIGH);
    digitalWrite(pinIN2, LOW);

    ledcWrite(0, 180);

    motorBerjalan = true;
    waktuMulaiMotor = millis();
    waktuMulaiFailsafe = millis();
    waktuTelurTerakhirMasuk = millis();
    durasiSudahDitambahkan = false;
    durasiMotorAkhir = (unsigned long)durasiTargetDetik * 1000;

    if (Firebase.ready())
    {
        Firebase.setBool(
            fbdo,
            "/aktuator/motor",
            true
        );
    }

    Serial.printf(
        "[PANEN] Target telur = %d (IR1_delta: %d + IR2_delta: %d)\n",
        targetPanen,
        telurBaruIR1,
        telurBaruIR2
    );

    Serial.println(
        "[MOTOR] ON"
    );
}

void motorOFF()
{
    digitalWrite(pinIN1, LOW);
    digitalWrite(pinIN2, LOW);

    ledcWrite(0, 0);

    motorBerjalan = false;
    motorSedangPushSisa = false;

    if (Firebase.ready())
    {
        Firebase.setBool(
            fbdo,
            "/aktuator/motor",
            false
        );
    }

    Serial.println(
        "[MOTOR] OFF"
    );
}

// ================= FIREBASE INIT =================

void initFirebase()
{
    if (!firebaseInitialized)
    {
        config.host = FIREBASE_HOST;

        config.signer.tokens.legacy_token =
            FIREBASE_AUTH;

        Firebase.begin(
            &config,
            &auth
        );

        Firebase.reconnectWiFi(true);

        firebaseInitialized = true;

        Serial.println(
            "[Firebase] Initialized"
        );
    }
}

// ================= AMBIL DATA AWAL =================

void fetchInitialData()
{
    if (!Firebase.ready())
        return;

    Serial.println(
        "[Firebase] Mengambil data terakhir..."
    );

    if (
        Firebase.getInt(
            fbdo,
            "/data/infra1"
        )
    )
    {
        jumlahTelur1 =
            fbdo.intData();

        Serial.printf(
            "IR1 terakhir: %d\n",
            jumlahTelur1
        );
    }

    if (
        Firebase.getInt(
            fbdo,
            "/data/infra2"
        )
    )
    {
        jumlahTelur2 =
            fbdo.intData();

        Serial.printf(
            "IR2 terakhir: %d\n",
            jumlahTelur2
        );
    }

    if (
        Firebase.getInt(
            fbdo,
            "/data/sudah_dipanen"
        )
    )
    {
        totalTelurSudahDipanen =
            fbdo.intData();

        Serial.printf(
            "Sudah dipanen: %d\n",
            totalTelurSudahDipanen
        );
    }

    ir1CheckpointPanen = jumlahTelur1;
    ir2CheckpointPanen = jumlahTelur2;

    dataTersinkron = true;

    Serial.println(
        "[Firebase] Sinkronisasi awal berhasil"
    );
}

// ================= NTP KE RTC =================

void sinkronisasiWaktuNTP()
{
    if (
        WiFi.status() !=
        WL_CONNECTED
    )
    {
        return;
    }

    configTime(
        7 * 3600,
        0,
        "id.pool.ntp.org",
        "time.nist.gov"
    );

    struct tm timeinfo;
        if (getLocalTime(&timeinfo))
    {
        rtc.adjust(
            DateTime(
                timeinfo.tm_year + 1900,
                timeinfo.tm_mon + 1,
                timeinfo.tm_mday,
                timeinfo.tm_hour,
                timeinfo.tm_min,
                timeinfo.tm_sec
            )
        );

        Serial.println(
            "[RTC] Sinkronisasi NTP berhasil"
        );
    }
    else
    {
        Serial.println(
            "[RTC] Gagal sinkronisasi NTP"
        );
    }
}

// ================= CEK KONEKSI =================
void cekKoneksiInternet()
{
    unsigned long nowMillis =
        millis();
    if (
        WiFi.status() !=
        WL_CONNECTED
    )
    {
        if (
            nowMillis -
            lastWiFiStatusPrint >=
            5000
        )
        {
            lastWiFiStatusPrint =
                nowMillis;
            Serial.println(
                "[WiFi] Terputus, mencoba reconnect..."
            );
        }
        if (
            nowMillis -
            lastReconnectAttempt >=
            intervalReconnect
        )
        {
            lastReconnectAttempt =
                nowMillis;
            WiFi.disconnect(false);
            WiFi.reconnect();
        }
        return;
    }

    if (!firebaseInitialized)
    {
        initFirebase();
    }

    if (
        !dataTersinkron &&
        Firebase.ready()
    )
    {
        fetchInitialData();
    }
}

// ================= SETUP =================
void setup()
{
    Serial.begin(115200);
    Wire.begin(
        pinSDA,
        pinSCL
    );
    if (!rtc.begin())
    {
        Serial.println(
            "[RTC] Tidak terdeteksi!"
        );

        while (1);
    }
    pinMode(pinIN1, OUTPUT);
    pinMode(pinIN2, OUTPUT);
    pinMode(pinIR1, INPUT);
    pinMode(pinIR2, INPUT);
    pinMode(pinIR3, INPUT);
    digitalWrite(pinIN1, LOW);
    digitalWrite(pinIN2, LOW);

    ledcSetup(
        0,
        30000,
        8
    );

    ledcAttachPin(
        pinENA,
        0
    );

    ledcWrite(
        0,
        0
    );

    WiFi.mode(WIFI_STA);
    WiFiManager wm;
    wm.setConnectTimeout(20);
    wm.setConfigPortalTimeout(180);
    bool wifiConnected =
        wm.autoConnect(
            "ESP32_Telurku"
        );

    if (wifiConnected)
    {
        Serial.println(
            "[WiFi] Terhubung"
        );
        sinkronisasiWaktuNTP();
        initFirebase();
        fetchInitialData();
    }
    else
    {
        Serial.println(
            "[WiFi] Gagal, sistem berjalan offline"
        );
    }
}

// ================= LOOP =================

void loop()
{
    unsigned long currentMillis =
        millis();
    cekKoneksiInternet();
    // ================= TASK 1 SENSOR =================
    if (
        currentMillis -
        lastTaskSensor >=
        intervalSensor
    )
    {
        lastTaskSensor =
            currentMillis;
        int s1 =
            digitalRead(pinIR1);
        int s2 =
             digitalRead(pinIR2);
        int s3 =
            digitalRead(pinIR3);
        //IR1
        if (
            lastStatusIR1 == HIGH &&
            s1 == LOW
        )
        {
            jumlahTelur1++;

            Serial.printf(
                "[IR1] Total: %d\n",
                jumlahTelur1
            );
        }
        //IR2
        if (
            lastStatusIR2 == HIGH &&
            s2 == LOW
        )
        {
            jumlahTelur2++;

            Serial.printf(
                "[IR2] Total: %d\n",
                jumlahTelur2
            );
        }
        //IR3 saat panen aktif
        if (
            modePanenAktif &&
            lastStatusIR3 == HIGH &&
            s3 == LOW
        )
        {
            jumlahTelurMasuk++;
            
            // Catat waktu telur terakhir masuk
            waktuTelurTerakhirMasuk = millis();

            Serial.printf(
                "[IR3] %d/%d\n",
                jumlahTelurMasuk,
                targetPanen
            );
        }

        lastStatusIR1 = s1;
        lastStatusIR2 = s2;
        lastStatusIR3 = s3;
    }

    // ================= CEK TARGET PANEN =================
    if (
        motorBerjalan &&
        modePanenAktif &&
        jumlahTelurMasuk >=
        targetPanen &&
        targetPanen > 0 &&
        waktuTargetTercapai == 0
    )
    {
        Serial.printf(
            "[PANEN] Target tercapai %d/%d\n",
            jumlahTelurMasuk,
            targetPanen
        );

        // Catat waktu target tercapai HANYA SEKALI
        waktuTargetTercapai = millis();
    }

    // ================= TASK 2 UPDATE FIREBASE =================
    if (
        currentMillis -
        lastTaskFirebase >=
        intervalFirebase
    )
    {
        lastTaskFirebase =
            currentMillis;
        if (Firebase.ready())
        {
            // RTC hanya dibaca saat dibutuhkan (task ini & task jadwal),
            // bukan setiap iterasi loop, supaya I2C tidak dibebani terus-menerus.
            DateTime now =
                rtc.now();
            FirebaseJson update;
            update.set(
                "infra1",
                jumlahTelur1
            );
            update.set(
                "infra2",
                jumlahTelur2
            );
            update.set(
                "infra1_checkpoint_panen",
                ir1CheckpointPanen
            );
            update.set(
                "infra2_checkpoint_panen",
                ir2CheckpointPanen
            );
            update.set(
                "infra3",
                jumlahTelurMasuk
            );
            update.set(
                "target_panen",
                targetPanen
            );
            update.set(
                "total_harus_kebak",
                totalTelurHarusKeBak
            );
            update.set(
                "sudah_dipanen",
                totalTelurSudahDipanen
            );
            update.set(
                "sisa_panen",
                targetPanen -
                jumlahTelurMasuk
            );
            update.set(
                "rtc_unix",
                (long long)
                now.unixtime()
            );
            update.set(
                "reset_counter_pending",
                resetCounterPending ? 1 : 0
            );
            update.set(
                "last_update",
                now.timestamp()
            );
            if (
                Firebase.updateNode(
                    fbdo,
                    "/data",
                    update
                )
            )
            {
                Serial.println(
                    "[Firebase] Data berhasil dikirim"
                );
            }
            else
            {
                Serial.print(
                    "[Firebase] Gagal update: "
                );

                Serial.println(
                    fbdo.errorReason()
                );
            }
        }
        else
        {
            Serial.println(
                "[Firebase] Offline"
            );
        }
    }

    // ================= TASK 3 RESET WIFI =================
    if (
        currentMillis -
        lastTaskResetWiFi >=
        intervalResetWiFi
    )
    {
        lastTaskResetWiFi =
            currentMillis;

        // RESET WIFI
        if (
            Firebase.ready() &&
            Firebase.getBool(
                fbdo,
                "/kontrol/reset_wifi"
            )
        )
        {
            if (fbdo.boolData())
            {
                Firebase.setBool(
                    fbdo,
                    "/kontrol/reset_wifi",
                    false
                );
                delay(500);

                WiFiManager wm;
                wm.resetSettings();
                Serial.println(
                    "[WiFi] Reset konfigurasi"
                );
                ESP.restart();
            }
        }

        // RESET COUNTER
        if (
            Firebase.ready() &&
            Firebase.getBool(
                fbdo,
                "/kontrol/reset_counter"
            )
        )
        {
            if (fbdo.boolData())
            {
                // Set flag: reset akan dilakukan setelah panen selesai
                // Ini memastikan panen sore bisa berjalan dengan checkpoint yang benar
                resetCounterPending = true;

                Firebase.setBool(
                    fbdo,
                    "/kontrol/reset_counter",
                    false
                );
                Serial.println(
                    "[RESET] Menunggu panen selesai untuk melakukan reset counter..."
                );
            }
        }
    }

    // ================= TASK 4 PENJADWALAN =================
  if (
      !motorBerjalan &&
      currentMillis -
      lastTaskJadwal >=
      intervalJadwal
  )
  {
      lastTaskJadwal =
          currentMillis;
      if (
          Firebase.ready() &&
          Firebase.getJSON(
              fbdo,
              "/kontrol/penjadwalan"
          )
      )
      {
          DateTime now =
              rtc.now();
          char jamSekarang[6];
          sprintf(
              jamSekarang,
              "%02d:%02d",
              now.hour(),
              now.minute()
          );
          FirebaseJson &json =
              fbdo.jsonObject();
          size_t len =
              json.iteratorBegin();
          String key;
          String value;
          int type;
          for (
              size_t i = 0;
              i < len;
              i++
          )
          {
              json.iteratorGet(
                  i,
                  type,
                  key,
                  value
              );
              if (
                  type ==
                  FirebaseJson::JSON_OBJECT
              )
              {
                  FirebaseJsonData d;
                  bool aktif =
                      false;
                  String jamJadwal =
                      "";
                  String durStr =
                      "";
                  // ===== PATH TANPA OPERATOR + =====
                  String pathAktif = key;
                  pathAktif.concat("/aktif");
                  String pathJam = key;
                  pathJam.concat("/jam");
                  String pathDurasi = key;
                  pathDurasi.concat("/durasi");
                  // ===============================
                 if (json.get(d, pathAktif.c_str()))
                    aktif = d.boolValue;

                if (json.get(d, pathJam.c_str()))
                    jamJadwal = d.stringValue;

                if (json.get(d, pathDurasi.c_str()))
                    durStr = d.stringValue;

                if (aktif && jamJadwal == String(jamSekarang))
                {
                    // proses selanjutnya
                }
                  {
                      durStr.trim();
                        durStr.toLowerCase();

                        if (durStr.indexOf("detik") != -1)
                        {
                            durasiTargetDetik =
                                durStr.substring(0, durStr.indexOf("detik")).toInt();
                        }
                        else if (durStr.indexOf("menit") != -1)
                        {
                            durasiTargetDetik =
                                durStr.substring(0, durStr.indexOf("menit")).toFloat() * 60;
                        }
                        else
                        {
                            // Jika hanya angka, dianggap menit (agar kompatibel dengan data lama)
                            durasiTargetDetik =
                                durStr.toFloat() * 60;
                        }

                        if (durasiTargetDetik <= 0)
                        {
                            durasiTargetDetik = 60;
                        }
                      
                      Serial.printf(
                            "[JADWAL] Durasi Firebase: %s -> %d detik\n",
                            durStr.c_str(),
                            durasiTargetDetik
                    );
                      Serial.printf(
                          "[JADWAL] Motor ON %d detik\n",
                          durasiTargetDetik
                      );

                      motorON();
                      break;
                  }
              }
          }

          json.iteratorEnd();
      }
  }

    // ================= TASK 5 MONITOR MOTOR =================

    if (motorBerjalan)
    {
        unsigned long waktuBerjalan =
            millis() -
            waktuMulaiMotor;
        bool durasiHabis =
            waktuBerjalan >=
            durasiMotorAkhir;
        bool failsafeHabis =
            millis() -
            waktuMulaiFailsafe >=
            timeoutFailsafe;

        // ===== KONDISI 1: Target sudah tercapai =====
        // Motor OFF 10 detik setelah target tercapai (bukan mengikuti durasi)
        if (waktuTargetTercapai != 0)
        {
            unsigned long waktuSetelahTarget =
                millis() -
                waktuTargetTercapai;

            if (waktuSetelahTarget >= timeoutMotorAfterTarget)
            {
                Serial.printf(
                    "[PANEN] Semua telur masuk bak (%d/%d)\n",
                    jumlahTelurMasuk,
                    targetPanen
                );

                totalTelurSudahDipanen +=
                    jumlahTelurMasuk;
                // Update checkpoint panen saat panen berhasil
                // Ini akan dipakai untuk panen berikutnya (panen sore)
                ir1CheckpointPanen = jumlahTelur1;
                ir2CheckpointPanen = jumlahTelur2;

                Serial.printf(
                    "[PANEN] Checkpoint updated: IR1=%d, IR2=%d (siap untuk panen berikutnya)\n",
                    ir1CheckpointPanen,
                    ir2CheckpointPanen
                );

                modePanenAktif = false;
                waktuTargetTercapai = 0;
                motorOFF();

                // ===== RESET COUNTER JIKA PENDING =====
                // Reset dilakukan EVENT-BASED setelah panen selesai, bukan time-based
                if (resetCounterPending)
                {
                    jumlahTelur1 = 0;
                    jumlahTelur2 = 0;
                    ir1CheckpointPanen = 0;
                    ir2CheckpointPanen = 0;
                    jumlahTelurMasuk = 0;
                    totalTelurSudahDipanen = 0;
                    targetPanen = 0;
                    durasiSudahDitambahkan = false;
                    durasiMotorAkhir = 0;
                    waktuTelurTerakhirMasuk = 0;
                    motorSedangPushSisa = false;
                    resetCounterPending = false;
                    Serial.println(
                        "[RESET] Counter berhasil direset setelah panen selesai"
                    );
                }
            }
            else
            {
                // Hanya print countdown setiap detik (saat countdown berubah)
                static unsigned long lastCountdownPrint = 0;
                unsigned long currentCountdown = (timeoutMotorAfterTarget - waktuSetelahTarget) / 1000;
                
                if (lastCountdownPrint != currentCountdown)
                {
                    lastCountdownPrint = currentCountdown;
                    
                    Serial.printf(
                        "[PANEN] Menunggu OFF: %lu detik lagi\n",
                        currentCountdown
                    );
                }
            }
        }

        // ===== KONDISI 2: Durasi habis tapi target belum tercapai =====
        // Ada telur tertinggal di konveyor, perpanjang durasi untuk push

        else if (
            durasiHabis &&
            jumlahTelurMasuk <
            targetPanen
        )
        {
            // Cek apakah ada telur yang masuk dalam durasi
            // Jika tidak ada telur sejak durasi habis dan belum pernah ditambah
            if (
                !durasiSudahDitambahkan &&
                millis() -
                waktuTelurTerakhirMasuk >=
                durasiMotorAkhir
            )
            {
                // Perpanjang durasi motor sebesar 2x dari durasi awal
                unsigned long durasiTambahan =
                    (unsigned long)durasiTargetDetik * 1000 * 2;
                durasiMotorAkhir +=
                    durasiTambahan;
                durasiSudahDitambahkan =
                    true;
                motorSedangPushSisa =
                    true;
                Serial.printf(
                    "[PANEN] Ada telur tertinggal! Durasi perpanjang 2x: %lu detik\n",
                    durasiMotorAkhir / 1000
                );

                Serial.printf(
                    "[PANEN] Menunggu telur masuk: %d/%d\n",
                    jumlahTelurMasuk,
                    targetPanen
                );
            }
            // Motor tetap jalan, menunggu sisa telur masuk bak
            // atau sampai failsafe 5 menit tercapai
            else
            {
                Serial.printf(
                    "[PANEN] Menunggu telur masuk: %d/%d\n",
                    jumlahTelurMasuk,
                    targetPanen
                );
            }
        }

        // ===== KONDISI 3: Failsafe 5 menit =====
        // Motor OFF paksa jika timeout 5 menit

        if (failsafeHabis)
        {
            Serial.println(
                "[FAILSAFE] Timeout 5 menit"
            );
            totalTelurSudahDipanen +=
                jumlahTelurMasuk;
            // Update checkpoint panen saat failsafe
            ir1CheckpointPanen = jumlahTelur1;
            ir2CheckpointPanen = jumlahTelur2;
            Serial.printf(
                "[FAILSAFE] Checkpoint updated: IR1=%d, IR2=%d\n",
                ir1CheckpointPanen,
                ir2CheckpointPanen
            );
            modePanenAktif = false;
            waktuTargetTercapai = 0;
            motorOFF();
            // ===== RESET COUNTER JIKA PENDING (FAILSAFE) =====
            // Jika failsafe terjadi dan reset pending, jalankan reset
            if (resetCounterPending)
            {
                jumlahTelur1 = 0;
                jumlahTelur2 = 0;
                ir1CheckpointPanen = 0;
                ir2CheckpointPanen = 0;
                jumlahTelurMasuk = 0;
                totalTelurSudahDipanen = 0;
                targetPanen = 0;
                durasiSudahDitambahkan = false;
                durasiMotorAkhir = 0;
                waktuTelurTerakhirMasuk = 0;
                motorSedangPushSisa = false;
                resetCounterPending = false;
                Serial.println(
                    "[RESET] Counter direset setelah failsafe"
                );
            }
        }
    }
}