#include <ESP8266WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <RTClib.h>
#include <DHT.h>
#include <Firebase_ESP_Client.h>
#include <EEPROM.h>  // [EEPROM]

// WiFi Credentials
const char* ssid = "Test123";
const char* password = "a?=31@#daE313-=;l.";

// Firebase credentials
#define API_KEY "AIzaSyBCe-30GPJRp0p7psDH4t2D01WHPcSNIqQ"
#define DATABASE_URL "https://apkrafi-default-rtdb.firebaseio.com/"

// Firebase objects
FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

// Relay pins for pumps
const int pumpRelayPins[4] = {D4, D5, D6, D7};

// I2C pins for DS3231 RTC
#define SDA_PIN D2
#define SCL_PIN D1

// DHT sensor setup
#define DHTPIN D3
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

// Soil moisture sensor
#define SOIL_MOISTURE_PIN A0
int soilMoisturePercent = 0;
int rawMoisture = 0;

bool pumpShouldRun[4] = {false, false, false, false};

unsigned long lastRTCSync = 0;
const unsigned long rtcSyncInterval = 3600000;

bool pump3Enabled = true;

WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "time.google.com", 7 * 3600, 60000);
RTC_DS3231 rtc;
bool wifiConnected = false;
unsigned long lastReconnectAttempt = 0;
const unsigned long reconnectInterval = 10000;

// [MOD] Firebase-controlled schedule variables
uint8_t fbPumpStartHour2 = 22, fbPumpStartMinute2 = 3, fbPumpEndHour2 = 22, fbPumpEndMinute2 = 5;
uint8_t fbPumpStartHour3 = 2, fbPumpStartMinute3 = 15, fbPumpEndHour3 = 2, fbPumpEndMinute3 = 50;
uint8_t fbPumpStartHour4 = 2, fbPumpStartMinute4 = 15, fbPumpEndHour4 = 2, fbPumpEndMinute4 = 50;
bool fbPump3Enabled = true;
unsigned long lastFirebaseSync = 0;
const unsigned long firebaseSyncInterval = 2000;
bool firebaseInitialized = false;

// [EEPROM] EEPROM setup
#define EEPROM_SIZE 64

void saveScheduleToEEPROM() {
  EEPROM.write(0, fbPumpStartHour2);
  EEPROM.write(1, fbPumpStartMinute2);
  EEPROM.write(2, fbPumpEndHour2);
  EEPROM.write(3, fbPumpEndMinute2);

  EEPROM.write(4, fbPumpStartHour3);
  EEPROM.write(5, fbPumpStartMinute3);
  EEPROM.write(6, fbPumpEndHour3);
  EEPROM.write(7, fbPumpEndMinute3);

  EEPROM.write(8, fbPumpStartHour4);
  EEPROM.write(9, fbPumpStartMinute4);
  EEPROM.write(10, fbPumpEndHour4);
  EEPROM.write(11, fbPumpEndMinute4);

  EEPROM.write(12, fbPump3Enabled);
  EEPROM.commit();
}

void loadScheduleFromEEPROM() {
  fbPumpStartHour2 = EEPROM.read(0);
  fbPumpStartMinute2 = EEPROM.read(1);
  fbPumpEndHour2 = EEPROM.read(2);
  fbPumpEndMinute2 = EEPROM.read(3);

  fbPumpStartHour3 = EEPROM.read(4);
  fbPumpStartMinute3 = EEPROM.read(5);
  fbPumpEndHour3 = EEPROM.read(6);
  fbPumpEndMinute3 = EEPROM.read(7);

  fbPumpStartHour4 = EEPROM.read(8);
  fbPumpStartMinute4 = EEPROM.read(9);
  fbPumpEndHour4 = EEPROM.read(10);
  fbPumpEndMinute4 = EEPROM.read(11);

  fbPump3Enabled = EEPROM.read(12);
}

void connectToWiFi() {
  Serial.println("Connecting to WiFi...");
  WiFi.begin(ssid, password);
  int attempt = 0;
  while (WiFi.status() != WL_CONNECTED && attempt < 20) {
    delay(500);
    Serial.print(".");
    attempt++;
  }
  wifiConnected = WiFi.status() == WL_CONNECTED;
  Serial.println(wifiConnected ? "\nWiFi connected." : "\nFailed to connect to WiFi.");
}

void syncRTCTime() {
  if (wifiConnected) {
    timeClient.update();
    DateTime now = DateTime(timeClient.getEpochTime());
    rtc.adjust(now);
    Serial.print("RTC synced with NTP time: ");
    Serial.println(now.timestamp());
  }
}

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

  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump2/startHour")) fbPumpStartHour2 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump2/startMinute")) fbPumpStartMinute2 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump2/endHour")) fbPumpEndHour2 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump2/endMinute")) fbPumpEndMinute2 = fbdo.to<int>();

  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump3/startHour")) fbPumpStartHour3 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump3/startMinute")) fbPumpStartMinute3 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump3/endHour")) fbPumpEndHour3 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump3/endMinute")) fbPumpEndMinute3 = fbdo.to<int>();

  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump4/startHour")) fbPumpStartHour4 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump4/startMinute")) fbPumpStartMinute4 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump4/endHour")) fbPumpEndHour4 = fbdo.to<int>();
  if (Firebase.RTDB.getInt(&fbdo, "/schedule/pump4/endMinute")) fbPumpEndMinute4 = fbdo.to<int>();

  if (Firebase.RTDB.getBool(&fbdo, "/schedule/pump3Enabled")) fbPump3Enabled = fbdo.to<bool>();

  saveScheduleToEEPROM();
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  EEPROM.begin(EEPROM_SIZE);
  loadScheduleFromEEPROM();

  for (int i = 0; i < 4; i++) {
    pinMode(pumpRelayPins[i], OUTPUT);
    digitalWrite(pumpRelayPins[i], HIGH);
  }

  Wire.begin(SDA_PIN, SCL_PIN);
  if (!rtc.begin()) Serial.println("Couldn't find RTC");

  dht.begin();
  connectToWiFi();
  if (wifiConnected) {
    syncRTCTime();
    lastRTCSync = millis();
  }

  config.api_key = API_KEY;
  config.database_url = DATABASE_URL;

  if (wifiConnected) {
    Firebase.begin(&config, &auth);
    Firebase.reconnectWiFi(true);

    if (!Firebase.signUp(&config, &auth, "", "")) {
      Serial.printf("Firebase signUp failed: %s\n", config.signer.signupError.message.c_str());
    } else {
      Serial.println("Anonymous sign-in successful.");
    }

    unsigned long timeout = millis();
    while (!Firebase.ready() && millis() - timeout < 10000) {
      delay(100);
    }

    if (Firebase.ready()) {
      Serial.println("Firebase is ready.");
    } else {
      Serial.println("Failed to connect to Firebase.");
    }
  } else {
    Serial.println("Skipped Firebase initialization (offline mode).");
  }
}

void loop() {
  static bool initialRTCSynced = false;
  if (!initialRTCSynced && wifiConnected) {
    syncRTCTime();
    initialRTCSynced = true;
  }

  if (millis() - lastFirebaseSync > firebaseSyncInterval) {
    fetchScheduleFromFirebase();
    lastFirebaseSync = millis();
  }

  if (WiFi.status() != WL_CONNECTED) {
    if (millis() - lastReconnectAttempt > reconnectInterval) {
      Serial.println("WiFi lost. Reconnecting...");
      WiFi.disconnect();
      WiFi.begin(ssid, password);
      lastReconnectAttempt = millis();
    }
    wifiConnected = false;
  } else {
    if (!wifiConnected) {
      Serial.println("WiFi reconnected.");
      wifiConnected = true;
    }
  }

  if (wifiConnected && !firebaseInitialized) {
    Serial.println("Initializing Firebase...");
    Firebase.begin(&config, &auth);
    Firebase.reconnectWiFi(true);

    if (!Firebase.signUp(&config, &auth, "", "")) {
      Serial.printf("Firebase signUp failed: %s\n", config.signer.signupError.message.c_str());
    } else {
      Serial.println("Anonymous sign-in successful.");
    }

    unsigned long timeout = millis();
    while (!Firebase.ready() && millis() - timeout < 10000) {
      delay(100);
    }

    if (Firebase.ready()) {
      Serial.println("Firebase is ready.");
      firebaseInitialized = true;
    } else {
      Serial.println("Failed to connect to Firebase.");
    }
  }

  if (wifiConnected && millis() - lastRTCSync > rtcSyncInterval) {
    syncRTCTime();
    lastRTCSync = millis();
  }

  DateTime nowTime;
  int currentHour, currentMinute, currentSecond;
  int currentMinutes;

  if (wifiConnected) {
    timeClient.update();
    currentHour = timeClient.getHours();
    currentMinute = timeClient.getMinutes();
    currentSecond = timeClient.getSeconds();
  } else {
    nowTime = rtc.now();
    currentHour = nowTime.hour();
    currentMinute = nowTime.minute();
    currentSecond = nowTime.second();
  }

  currentMinutes = currentHour * 60 + currentMinute;
  Serial.printf("Time: %02d:%02d:%02d\n", currentHour, currentMinute, currentSecond);

  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();
  if (!isnan(temperature) && !isnan(humidity)) {
    Serial.printf("Temperature: %.1f °C, Humidity: %.1f %%\n", temperature, humidity);
  } else {
    Serial.println("Failed to read from DHT sensor!");
  }

  rawMoisture = analogRead(SOIL_MOISTURE_PIN);
  const int WET_VALUE = 450;  
  const int DRY_VALUE = 1024;

  soilMoisturePercent = map(rawMoisture, DRY_VALUE, WET_VALUE, 0, 100);
  soilMoisturePercent = constrain(soilMoisturePercent, 0, 100);

  Serial.printf("Soil Moisture: %d %% (Raw: %d)\n", soilMoisturePercent, rawMoisture);

  if (Firebase.ready()) {
    Firebase.RTDB.setFloat(&fbdo, "/sensors/temperature", temperature);
    Firebase.RTDB.setFloat(&fbdo, "/sensors/humidity", humidity);
    Firebase.RTDB.setInt(&fbdo, "/sensors/soilMoisture", soilMoisturePercent);
  }

  pumpShouldRun[0] = (!isnan(temperature) && temperature >= 31.0);

  static bool pump2Running = false;
  static bool pump2SkippedDueToMoisture = false;
  const int soilMoistureThreshold = 60;
  int start2 = fbPumpStartHour2 * 60 + fbPumpStartMinute2;
  int end2 = fbPumpEndHour2 * 60 + fbPumpEndMinute2;

  if (currentMinutes == start2) {
    if (soilMoisturePercent <= soilMoistureThreshold) {
      pump2Running = true;
      pump2SkippedDueToMoisture = false;
      Serial.println("Pump 2 scheduled to start: soil is dry enough.");
    } else {
      pump2Running = false;
      pump2SkippedDueToMoisture = true;
      Serial.println("Pump 2 skipped due to sufficient soil moisture.");
    }
  }

  // Keep running only while time range AND moisture is still low
  if (currentMinutes >= start2 && currentMinutes < end2) {
    if (pump2Running && soilMoisturePercent > soilMoistureThreshold) {
      pump2Running = false; // stop immediately
      Serial.println("Pump 2 stopped early due to sufficient soil moisture.");
    }
    pumpShouldRun[1] = pump2Running && !pump2SkippedDueToMoisture;
  } else {
    pumpShouldRun[1] = false;
    pump2Running = false;
    pump2SkippedDueToMoisture = false;
  }
  // ------------------------------------------

  int start3 = fbPumpStartHour3 * 60 + fbPumpStartMinute3;
  int end3 = fbPumpEndHour3 * 60 + fbPumpEndMinute3;
  int start4 = fbPumpStartHour4 * 60 + fbPumpStartMinute4;
  int end4 = fbPumpEndHour4 * 60 + fbPumpEndMinute4;

  bool pump3Time = (currentMinutes >= start3 && currentMinutes < end3);
  bool pump4Time = (currentMinutes >= start4 && currentMinutes < end4);

  pumpShouldRun[2] = pump3Time && fbPump3Enabled;
  pumpShouldRun[3] = pump4Time && !fbPump3Enabled;

  Serial.printf("Scheduled pump: %s\n", fbPump3Enabled ? "Pump 3" : "Pump 4");

  for (int i = 0; i < 4; i++) {
    bool isOn = digitalRead(pumpRelayPins[i]) == LOW;
    if (pumpShouldRun[i] && !isOn) digitalWrite(pumpRelayPins[i], LOW);
    else if (!pumpShouldRun[i] && isOn) digitalWrite(pumpRelayPins[i], HIGH);
  }

  for (int i = 0; i < 4; i++) {
    bool isOn = digitalRead(pumpRelayPins[i]) == LOW;
    const char* statusText = isOn ? "ON" : "OFF";
    Serial.printf("Pump %d: %s\n", i + 1, statusText);
  }
  Serial.println("--------------------------");

  delay(1000);
}
