#include <WiFi.h>
#include <FirebaseESP32.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <ArduinoJson.h>
#include <time.h>

// WiFi credentials
const char* ssid = "Wokwi-GUEST";
const char* password = "";

// Firebase configuration
#define FIREBASE_HOST "https://dht22-bcd23-default-rtdb.firebaseio.com"
#define FIREBASE_AUTH "AIzaSyD4BRCQt5XLYbrAa1SwnX-ljSxkC0oKEGs"

// Indian Standard Time Configuration
#define IST_OFFSET_SECONDS 19800  // UTC+5:30 = 5.5 hours * 3600 seconds
#define IST_TIMEZONE_STRING "IST-5:30"

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

// NTP Client for time synchronization - using Indian NTP servers
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "time.nist.gov", 0, 60000); // No offset here, we'll handle IST manually

// Alternative NTP servers for better reliability in India
const char* ntpServers[] = {
  "pool.ntp.org",
  "time.nist.gov",
  "time.google.com",
  "0.in.pool.ntp.org",  // Indian NTP server
  "1.in.pool.ntp.org"   // Indian NTP server
};
const int numNtpServers = sizeof(ntpServers) / sizeof(ntpServers[0]);

// LED pins
const int LED_PINS[] = {2, 4, 18, 19}; // LED 1, LED 2, LED 3, LED 4
const int NUM_LEDS = 4;

// Timing variables
unsigned long lastScheduleCheck = 0;
unsigned long lastFirebaseSync = 0;
unsigned long lastTimeUpdate = 0;
unsigned long lastSystemCheck = 0;
unsigned long lastTimeDisplay = 0;
const unsigned long SCHEDULE_CHECK_INTERVAL = 30000; // Check every 30 seconds
const unsigned long FIREBASE_SYNC_INTERVAL = 10000;  // Sync every 10 seconds
const unsigned long TIME_UPDATE_INTERVAL = 1000;     // Update time every second
const unsigned long SYSTEM_CHECK_INTERVAL = 5000;    // Check system commands every 5 seconds
const unsigned long TIME_DISPLAY_INTERVAL = 1000;    // Display time every second

// Structure to hold schedule data
struct Schedule {
  String id;
  int ledNumber;
  String date;
  String startTime;
  String endTime;
  bool isActive;
  bool isScheduled;
};

// Global variables
std::vector<Schedule> schedules;
bool firebaseConnected = false;
bool ledStates[NUM_LEDS] = {false, false, false, false};
int currentNtpServerIndex = 0;

void setup() {
  Serial.begin(115200);
  Serial.println("ESP32 LED Scheduler Starting...");
  Serial.println("Configured for Indian Standard Time (IST - UTC+5:30)");
  
  // Initialize LED pins
  for (int i = 0; i < NUM_LEDS; i++) {
    pinMode(LED_PINS[i], OUTPUT);
    digitalWrite(LED_PINS[i], LOW);
    ledStates[i] = false;
    Serial.println("LED " + String(i + 1) + " initialized on pin " + String(LED_PINS[i]));
  }
  
  // Connect to WiFi
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("WiFi connected!");
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());
  
  // Initialize NTP client with IST timezone
  initializeNTPClient();
  
  // Configure system timezone for IST
  configureTimezone();
  
  // Configure Firebase
  config.host = FIREBASE_HOST;
  config.signer.tokens.legacy_token = FIREBASE_AUTH;
  
  // Initialize Firebase
  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
  
  // Test Firebase connection
  if (Firebase.ready()) {
    Serial.println("Firebase connected successfully!");
    firebaseConnected = true;
    
    // Initialize Firebase structure
    initializeFirebaseStructure();
    
    // Load existing schedules
    loadSchedulesFromAndroidApp();
  } else {
    Serial.println("Firebase connection failed!");
  }
  
  Serial.println("Setup complete. System ready!");
  Serial.println("Current IST time: " + getCurrentDateTimeIST());
  Serial.println("=====================================");
}

void initializeNTPClient() {
  timeClient.begin();
  // Don't set offset here - we'll handle IST conversion manually
  
  Serial.print("Synchronizing time with NTP server");
  int attempts = 0;
  while (!timeClient.update() && attempts < 10) {
    Serial.print(".");
    delay(1000);
    attempts++;
    
    // Try different NTP servers if current one fails
    if (attempts % 3 == 0 && attempts < 9) {
      currentNtpServerIndex = (currentNtpServerIndex + 1) % numNtpServers;
      timeClient.setPoolServerName(ntpServers[currentNtpServerIndex]);
      Serial.print(" [Trying " + String(ntpServers[currentNtpServerIndex]) + "]");
    }
  }
  
  if (timeClient.update()) {
    Serial.println();
    Serial.println("Time synchronized successfully!");
    Serial.println("NTP Server: " + String(ntpServers[currentNtpServerIndex]));
    Serial.println("UTC time: " + timeClient.getFormattedTime());
    Serial.println("IST time: " + getCurrentTimeIST());
  } else {
    Serial.println();
    Serial.println("Warning: Time synchronization failed. Using system time.");
  }
}

void configureTimezone() {
  // Configure system timezone for IST
  setenv("TZ", IST_TIMEZONE_STRING, 1);
  tzset();
  
  Serial.println("System timezone configured for IST");
}

void loop() {
  // Update time
  timeClient.update();
  
  // Display current time every second
  if (millis() - lastTimeDisplay > TIME_DISPLAY_INTERVAL) {
    Serial.println("Current IST: " + getCurrentDateTimeIST());
    lastTimeDisplay = millis();
  }
  
  // Update current time in Firebase for Android app
  if (millis() - lastTimeUpdate > TIME_UPDATE_INTERVAL) {
    updateCurrentTimeInFirebase();
    lastTimeUpdate = millis();
  }
  
  // Check Firebase connection and sync data
  if (millis() - lastFirebaseSync > FIREBASE_SYNC_INTERVAL) {
    if (Firebase.ready()) {
      if (!firebaseConnected) {
        Serial.println("Firebase reconnected!");
        firebaseConnected = true;
      }
      syncWithFirebase();
    } else {
      if (firebaseConnected) {
        Serial.println("Firebase connection lost!");
        firebaseConnected = false;
      }
    }
    lastFirebaseSync = millis();
  }
  
  // Check and execute schedules
  if (millis() - lastScheduleCheck > SCHEDULE_CHECK_INTERVAL) {
    checkAndExecuteSchedules();
    lastScheduleCheck = millis();
  }
  
  // Check for system commands (including reset)
  if (millis() - lastSystemCheck > SYSTEM_CHECK_INTERVAL) {
    handleSystemReset();
    lastSystemCheck = millis();
  }
  
  // Check for manual control commands
  handleManualControl();
  
  delay(100);
}

void initializeFirebaseStructure() {
  // Initialize system status for Android app
  Firebase.setBool(firebaseData, "/ledScheduler/systemRunning", true);
  Firebase.setString(firebaseData, "/ledScheduler/lastUpdate", getCurrentDateTimeIST());
  Firebase.setString(firebaseData, "/ledScheduler/timezone", "IST");
  Firebase.setString(firebaseData, "/ledScheduler/timezoneOffset", "+05:30");
  
  // Initialize device status and commands structure
  Firebase.setString(firebaseData, "/device/status", "running");
  Firebase.setString(firebaseData, "/device/lastBoot", getCurrentDateTimeIST());
  Firebase.setString(firebaseData, "/device/timezone", "IST");
  Firebase.setString(firebaseData, "/commands/system", "");
  
  // Initialize LED status for Android app
  for (int i = 0; i < NUM_LEDS; i++) {
    String ledPath = "/ledScheduler/led" + String(i + 1);
    Firebase.setBool(firebaseData, ledPath + "/state", false);
    Firebase.setBool(firebaseData, ledPath + "/isScheduled", false);
    Firebase.setBool(firebaseData, ledPath + "/isActive", false);
    Firebase.setBool(firebaseData, ledPath + "/manualControl", false);
    Firebase.setBool(firebaseData, ledPath + "/manualState", false);
  }
  
  Serial.println("Firebase structure initialized for Android app compatibility");
}

void updateCurrentTimeInFirebase() {
  String currentDateTime = getCurrentDateTimeIST();
  Firebase.setString(firebaseData, "/ledScheduler/currentDateTime", currentDateTime);
  Firebase.setString(firebaseData, "/ledScheduler/currentTimeIST", currentDateTime);
  Firebase.setString(firebaseData, "/device/lastSeen", currentDateTime);
}

void syncWithFirebase() {
  // Update system status
  Firebase.setBool(firebaseData, "/ledScheduler/systemRunning", true);
  Firebase.setString(firebaseData, "/ledScheduler/lastUpdate", getCurrentDateTimeIST());
  Firebase.setString(firebaseData, "/device/status", "running");
  
  // Update LED states
  for (int i = 0; i < NUM_LEDS; i++) {
    String ledPath = "/ledScheduler/led" + String(i + 1);
    Firebase.setBool(firebaseData, ledPath + "/state", ledStates[i]);
  }
  
  // Load schedules from Android app
  loadSchedulesFromAndroidApp();
  
  // Check for manual control commands
  checkManualCommands();
}

void loadSchedulesFromAndroidApp() {
  schedules.clear();
  
  // Load schedules from Android app structure
  for (int i = 1; i <= NUM_LEDS; i++) {
    String ledPath = "/ledScheduler/led" + String(i);
    
    // Check if LED has a schedule
    if (Firebase.getBool(firebaseData, ledPath + "/isScheduled")) {
      bool isScheduled = firebaseData.boolData();
      
      if (isScheduled) {
        Schedule schedule;
        schedule.id = "led" + String(i);
        schedule.ledNumber = i;
        schedule.isScheduled = true;
        
        // Get schedule data
        if (Firebase.getString(firebaseData, ledPath + "/scheduledDate")) {
          schedule.date = firebaseData.stringData();
        }
        if (Firebase.getString(firebaseData, ledPath + "/onTime")) {
          schedule.startTime = firebaseData.stringData();
        }
        if (Firebase.getString(firebaseData, ledPath + "/offTime")) {
          schedule.endTime = firebaseData.stringData();
        }
        if (Firebase.getBool(firebaseData, ledPath + "/isActive")) {
          schedule.isActive = firebaseData.boolData();
        }
        
        // Only add valid schedules
        if (!schedule.date.isEmpty() && !schedule.startTime.isEmpty() && !schedule.endTime.isEmpty()) {
          schedules.push_back(schedule);
          Serial.println("Loaded schedule for LED " + String(i) + ": " + schedule.date + " " + schedule.startTime + "-" + schedule.endTime + " (IST)");
        }
      }
    }
  }
  
  Serial.println("Loaded " + String(schedules.size()) + " schedules from Android app");
}

void checkManualCommands() {
  // Check for manual LED control commands from Android app
  for (int i = 1; i <= NUM_LEDS; i++) {
    String ledPath = "/ledScheduler/led" + String(i);
    
    // Check if manual control is enabled
    if (Firebase.getBool(firebaseData, ledPath + "/manualControl")) {
      bool manualControl = firebaseData.boolData();
      
      if (manualControl) {
        // Get manual state
        if (Firebase.getBool(firebaseData, ledPath + "/manualState")) {
          bool manualState = firebaseData.boolData();
          
          // Apply manual control
          digitalWrite(LED_PINS[i-1], manualState ? HIGH : LOW);
          ledStates[i-1] = manualState;
          
          Serial.println("Manual control: LED " + String(i) + " turned " + (manualState ? "ON" : "OFF") + " at " + getCurrentTimeIST() + " IST");
          
          // Update state in Firebase
          Firebase.setBool(firebaseData, ledPath + "/state", manualState);
          
          // Reset manual control flag
          Firebase.setBool(firebaseData, ledPath + "/manualControl", false);
        }
      }
    }
  }
}

void handleManualControl() {
  // Additional manual control logic can be added here
  // Currently handled in checkManualCommands()
}

void handleSystemReset() {
  if (Firebase.getString(firebaseData, "/commands/system")) {
    String command = firebaseData.stringData();
    
    if (command == "RESET") {
      Serial.println("System reset command received at " + getCurrentDateTimeIST() + " IST");
      
      // Update status before reset
      Firebase.setString(firebaseData, "/device/status", "resetting");
      Firebase.setString(firebaseData, "/device/lastReset", getCurrentDateTimeIST());
      
      // Turn off all LEDs
      for (int i = 0; i < NUM_LEDS; i++) {
        digitalWrite(LED_PINS[i], LOW);
        ledStates[i] = false;
        
        // Update LED states in Firebase
        String ledPath = "/ledScheduler/led" + String(i + 1);
        Firebase.setBool(firebaseData, ledPath + "/state", false);
        Firebase.setBool(firebaseData, ledPath + "/isActive", false);
      }
      
      // Clear the command
      Firebase.setString(firebaseData, "/commands/system", "");
      
      // Update final status
      Firebase.setString(firebaseData, "/device/status", "reset");
      Firebase.setBool(firebaseData, "/ledScheduler/systemRunning", false);
      
      Serial.println("All LEDs turned off. Restarting system...");
      
      delay(2000); // Give Firebase time to update
      ESP.restart();
    }
  }
}

void checkAndExecuteSchedules() {
  String currentDate = getCurrentDateIST();
  String currentTime = getCurrentTimeIST();
  
  Serial.println("Checking schedules - Current IST: " + currentDate + " " + currentTime);
  
  for (auto& schedule : schedules) {
    if (schedule.isScheduled && schedule.date == currentDate) {
      int ledIndex = schedule.ledNumber - 1;
      
      if (isCurrentTimeInSchedule(currentTime, schedule.startTime, schedule.endTime)) {
        // Time to turn on LED
        if (!ledStates[ledIndex]) {
          digitalWrite(LED_PINS[ledIndex], HIGH);
          ledStates[ledIndex] = true;
          
          Serial.println("Schedule executed: LED " + String(schedule.ledNumber) + " turned ON at " + getCurrentDateTimeIST() + " IST");
          
          // Update Firebase
          String ledPath = "/ledScheduler/led" + String(schedule.ledNumber);
          Firebase.setBool(firebaseData, ledPath + "/state", true);
          Firebase.setBool(firebaseData, ledPath + "/isActive", true);
          Firebase.setString(firebaseData, ledPath + "/lastActivated", getCurrentDateTimeIST());
        }
      } else if (isTimeAfter(currentTime, schedule.endTime)) {
        // Time to turn off LED (schedule ended)
        if (ledStates[ledIndex]) {
          digitalWrite(LED_PINS[ledIndex], LOW);
          ledStates[ledIndex] = false;
          
          Serial.println("Schedule ended: LED " + String(schedule.ledNumber) + " turned OFF at " + getCurrentDateTimeIST() + " IST");
          
          // Update Firebase
          String ledPath = "/ledScheduler/led" + String(schedule.ledNumber);
          Firebase.setBool(firebaseData, ledPath + "/state", false);
          Firebase.setBool(firebaseData, ledPath + "/isActive", false);
          Firebase.setBool(firebaseData, ledPath + "/isScheduled", false);
          Firebase.setString(firebaseData, ledPath + "/lastCompleted", getCurrentDateTimeIST());
          
          // Mark schedule as completed
          schedule.isScheduled = false;
        }
      }
    }
  }
}

String getCurrentDateTimeIST() {
  timeClient.update();
  time_t epochTime = timeClient.getEpochTime();
  
  // Add IST offset to UTC time (only once!)
  epochTime += IST_OFFSET_SECONDS;
  
  struct tm *ptm = gmtime((time_t *)&epochTime);
  
  char dateTime[32];
  sprintf(dateTime, "%04d-%02d-%02d %02d:%02d:%02d", 
          ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
          ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
  
  return String(dateTime);
}

String getCurrentDateIST() {
  timeClient.update();
  time_t epochTime = timeClient.getEpochTime();
  epochTime += IST_OFFSET_SECONDS;
  
  struct tm *ptm = gmtime((time_t *)&epochTime);
  
  char date[16];
  sprintf(date, "%04d-%02d-%02d", 
          ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday);
  
  return String(date);
}

String getCurrentTimeIST() {
  timeClient.update();
  time_t epochTime = timeClient.getEpochTime();
  epochTime += IST_OFFSET_SECONDS;
  
  struct tm *ptm = gmtime((time_t *)&epochTime);
  
  char time[16];
  sprintf(time, "%02d:%02d:%02d", 
          ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
  
  return String(time);
}

// Alternative method using system time (as backup)
String getSystemTimeIST() {
  time_t now;
  struct tm timeinfo;
  time(&now);
  localtime_r(&now, &timeinfo);
  
  char dateTime[32];
  strftime(dateTime, sizeof(dateTime), "%Y-%m-%d %H:%M:%S", &timeinfo);
  
  return String(dateTime);
}

bool isCurrentTimeInSchedule(String currentTime, String startTime, String endTime) {
  int currentMinutes = timeToMinutes(currentTime);
  int startMinutes = timeToMinutes(startTime);
  int endMinutes = timeToMinutes(endTime);
  
  // Handle cases where end time is on the next day
  if (endMinutes < startMinutes) {
    return (currentMinutes >= startMinutes || currentMinutes <= endMinutes);
  }
  
  return (currentMinutes >= startMinutes && currentMinutes <= endMinutes);
}

bool isTimeAfter(String currentTime, String compareTime) {
  int currentMinutes = timeToMinutes(currentTime);
  int compareMinutes = timeToMinutes(compareTime);
  
  return currentMinutes > compareMinutes;
}

int timeToMinutes(String timeStr) {
  int colonIndex = timeStr.indexOf(':');
  if (colonIndex == -1) return 0;
  
  int hours = timeStr.substring(0, colonIndex).toInt();
  int minutes = timeStr.substring(colonIndex + 1).toInt();
  
  return hours * 60 + minutes;
}