#include <SPI.h>
#include <MFRC522.h>
#include <Preferences.h>
#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <Arduino.h>

// Tasks
TaskHandle_t rfid_read;
TaskHandle_t manual_override_task;
TaskHandle_t pump_algorithm;
TaskHandle_t wifi_manager_task;
TaskHandle_t wifi_config_task;

Preferences prefs;
#define PREFS_NAMESPACE "override"
#define MAX_PENDING_OVERRIDES 1000

WebServer server(80);

// WiFi Globals
String wifi1_ssid = "";
String wifi1_pass = "";
String wifi2_ssid = "";
String wifi2_pass = "";

// Globals
int pump_status = 0;
unsigned long lastRunTime = 0;
String tags = ",3456544257,3456544258";
const unsigned long interval = 250;  // ms

// PINS
#define RST_PIN 22
#define SS_PIN 5
#define manual_override 27
#define pump_relay 25
#define TANK_SENSPOR_PIN 32
#define FLOW_SENSOR_PIN 4
#define pulse_diff_threshold 10

// Tanks Sensor Settings
#define VREF 3.3
#define ADC_MAX 4095.0
#define R_SENSE 216.0
#define MA_EMPTY 3.30
#define MA_KNOWN 3.77
#define DEPTH_KNOWN 205.0
#define SAMPLE_COUNT 20
#define ALPHA 0.1

const float mm_per_mA = DEPTH_KNOWN / (MA_KNOWN - MA_EMPTY);
float smoothedCurrent = MA_EMPTY;
const char* serverName = "api.fuelsupplier.elegantwork.co.za";

WiFiClient client;

// System Settings
String Unit_ID = "A123";
// Global to store the current RFID tag and type
String current_rfid_tag = "0"; // Default to 0 as per your request
String current_tran_type = "UNKNOWN";

volatile long pulse = 0;
volatile long old_pulse = 0;
volatile long pulse_dif = 0;
int flow_counter = 0;
int time_out_s = 20;
int relay_status = 0;
int tank_level_counter = 0;
int auth_check = 0;
int M_O_status = 0;
int timer = 0;
float ml_per_pulse;
float rands_per_liter;
int interent = 0;


HardwareSerial MySerial(2);
MFRC522 rfid(SS_PIN, RST_PIN);

// Send to LCD
void send_data(String message) {
  MySerial.println(message);
}

void IRAM_ATTR increase() {
  pulse++;
}

// New struct to hold the combined data
struct OverrideEntry {
  unsigned long timestamp;
  int type;
  String rfid_tag;
};

// A more descriptive key for the data
String override_key(int i) {
  return "entry_" + String(i);
}

// Function to serialize the struct into a string
String serialize_entry(OverrideEntry entry) {
  return String(entry.timestamp) + "," + String(entry.type) + "," + entry.rfid_tag;
}

// Function to deserialize a string back into a struct
OverrideEntry deserialize_entry(String data_string) {
  OverrideEntry entry;
  int first_comma = data_string.indexOf(',');
  int second_comma = data_string.indexOf(',', first_comma + 1);

  entry.timestamp = data_string.substring(0, first_comma).toInt();
  entry.type = data_string.substring(first_comma + 1, second_comma).toInt();
  entry.rfid_tag = data_string.substring(second_comma + 1);

  return entry;
}

// --- Storage Functions ---

void save_to_storage(OverrideEntry new_entry) {
  prefs.begin(PREFS_NAMESPACE, false);

  for (int i = 0; i < MAX_PENDING_OVERRIDES; i++) {
    String key = override_key(i);
    if (!prefs.isKey(key.c_str())) {
      // Serialize the new entry into a single string
      String combined_data = serialize_entry(new_entry);
      prefs.putString(key.c_str(), combined_data);
      Serial.println("Saved entry to flash: " + combined_data);
      break;
    }
  }
  prefs.end();
}


// ========== WiFi Logic ==========
void handleRoot() {
  prefs.begin("wifi", true);
  String ssid1 = prefs.getString("wifi1_ssid", "");
  String pass1 = prefs.getString("wifi1_pass", "");
  String ssid2 = prefs.getString("wifi2_ssid", "");
  String pass2 = prefs.getString("wifi2_pass", "");
  String mlperpulse = prefs.getString("mlperpulse", "10");
  String rpl = prefs.getString("rpl", "20.33");
  String pump_id = prefs.getString("pump_id", Unit_ID);
  prefs.end();

  String page = R"rawliteral(
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>WiFi & Pump Setup</title>
    <style>
  /* Google Fonts - you can load from web */
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');

  body {
    margin: 0; padding: 20px;
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
      Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
    background: #f5f7fa;
    color: #333;
  }
  h2 {
    font-weight: 600;
    margin-bottom: 24px;
    color: #222;
    text-align: center;
  }
  form {
    max-width: 400px;
    margin: 0 auto;
    background: #fff;
    padding: 30px 25px;
    border-radius: 12px;
    box-shadow: 0 10px 20px rgba(0,0,0,0.08);
  }
  label {
    font-weight: 600;
    display: block;
    margin-bottom: 6px;
    margin-top: 16px;
    color: #555;
  }
  input[type="text"], input[type="password"], input[type="number"] {
    width: 100%;
    padding: 10px 14px;
    font-size: 16px;
    border: 1.8px solid #ddd;
    border-radius: 8px;
    box-sizing: border-box;
    transition: border-color 0.3s ease;
  }
  input[type="text"]:focus, input[type="password"]:focus, input[type="number"]:focus {
    outline: none;
    border-color: #3b82f6; /* nice blue */
    box-shadow: 0 0 8px rgba(59,130,246,0.3);
  }
  input[type="submit"] {
    margin-top: 28px;
    width: 100%;
    background: linear-gradient(135deg, #3b82f6, #2563eb);
    border: none;
    color: white;
    font-size: 18px;
    padding: 14px;
    border-radius: 12px;
    cursor: pointer;
    font-weight: 700;
    transition: background 0.3s ease;
  }
  input[type="submit"]:hover {
    background: linear-gradient(135deg, #2563eb, #1d4ed8);
  }
  /* Responsive text center on small */
  @media (max-width: 420px) {
    body {
      padding: 15px 10px;
    }
    form {
      padding: 20px 15px;
    }
  }
    </style>
    </head>
    <body>
  <h2>WiFi & Pump Setup</h2>
  <form action="/save" method="POST">
    <label for="ssid1">WiFi 1 SSID:</label>
    <input id="ssid1" name="ssid1" type="text" value=")rawliteral"
                + ssid1 + R"rawliteral(" />

    <label for="pass1">WiFi 1 PASS:</label>
    <input id="pass1" name="pass1" type="text" value=")rawliteral"
                + pass1 + R"rawliteral(" />

    <label for="ssid2">WiFi 2 SSID:</label>
    <input id="ssid2" name="ssid2" type="text" value=")rawliteral"
                + ssid2 + R"rawliteral(" />

    <label for="pass2">WiFi 2 PASS:</label>
    <input id="pass2" name="pass2" type="text" value=")rawliteral"
                + pass2 + R"rawliteral(" />

    <label for="mlperpulse">mlperpulse:</label>
    <input id="mlperpulse" name="mlperpulse" type="number" step="0.01" value=")rawliteral"
                + mlperpulse + R"rawliteral(" />

    <label for="rpl">Rands per liter (rpl):</label>
    <input id="rpl" name="rpl" type="number" step="0.01" value=")rawliteral"
                + rpl + R"rawliteral(" />

    <label for="pump_id">Pump ID:</label>
    <input id="pump_id" name="pump_id" type="text" value=")rawliteral"
                + pump_id + R"rawliteral(" />

    <input type="submit" value="Save" />
  </form>
    </body>
    </html>
    )rawliteral";

  server.send(200, "text/html", page);
}

void handleSave() {
  prefs.begin("wifi", false);
  prefs.putString("wifi1_ssid", server.arg("ssid1"));
  prefs.putString("wifi1_pass", server.arg("pass1"));
  prefs.putString("wifi2_ssid", server.arg("ssid2"));
  prefs.putString("wifi2_pass", server.arg("pass2"));
  prefs.putString("mlperpulse", server.arg("mlperpulse"));
  prefs.putString("rpl", server.arg("rpl"));
  prefs.putString("pump_id", server.arg("pump_id"));
  prefs.end();
  server.send(200, "text/html", "<h3 style='font-weight: 600; margin-bottom: 24px; color: #222; text-align: center;'>Saved</h3>");
  prefs.begin("wifi", true);
  ml_per_pulse = prefs.getString("mlperpulse", "0.00").toFloat();
  rands_per_liter = prefs.getString("rpl", "0.00").toFloat();
  Unit_ID = prefs.getString("pump_id", Unit_ID);  // override default if saved
  send_data("mlperpulse=" + String(ml_per_pulse));
  send_data("rpl=" + String(rands_per_liter));
  send_data("pump_id=" + Unit_ID);
  prefs.end();
}


void wifiConfigTask(void* parameter) {
  WiFi.softAP(Unit_ID.c_str(), Unit_ID.c_str());
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);
  server.on("/", handleRoot);
  server.on("/save", HTTP_POST, handleSave);
  server.begin();

  while (true) {
    server.handleClient();
    delay(10);
  }
}

bool tryConnect(const char* ssid, const char* pass, int attempts = 10) {
  WiFi.begin(ssid, pass);
  for (int i = 0; i < attempts; i++) {
    if (WiFi.status() == WL_CONNECTED) return true;
    delay(1000);
  }
  return false;
}

int getSignalLevel(int rssi) {
  if (rssi > -60) return 2;       // Excellent signal
  else if (rssi > -75) return 1;  // Fair signal
  else return 0;

  // Poor or no signal
}

bool testInternet() {
  WiFiClient client;
  return client.connect("8.8.8.8", 53);  // DNS ping
}

void wifiManagerTask(void* parameter) {
  bool connectedToWiFi2 = false;

  while (true) {
    prefs.begin("wifi", true);
    wifi1_ssid = prefs.getString("wifi1_ssid", "");
    wifi1_pass = prefs.getString("wifi1_pass", "");
    wifi2_ssid = prefs.getString("wifi2_ssid", "");
    wifi2_pass = prefs.getString("wifi2_pass", "");
    prefs.end();

    int signalLevel = 0;
    bool hasInternet = false;

    if (wifi1_ssid != "" && tryConnect(wifi1_ssid.c_str(), wifi1_pass.c_str())) {
      Serial.println("Connected to WiFi 1");
      signalLevel = getSignalLevel(WiFi.RSSI());
      hasInternet = testInternet();  // Check if internet is available
      send_data("wifi_name=" + wifi1_ssid + ",wifi=1,signal=" + String(signalLevel) + ",internet=" + String(hasInternet ? 1 : 0));
      connectedToWiFi2 = false;

    } else if (wifi2_ssid != "" && tryConnect(wifi2_ssid.c_str(), wifi2_pass.c_str())) {
      Serial.println("Connected to WiFi 2");
      signalLevel = getSignalLevel(WiFi.RSSI());
      hasInternet = testInternet();
      send_data("wifi_name=" + wifi2_ssid + ",wifi=1,signal=" + String(signalLevel) + ",internet=" + String(hasInternet ? 1 : 0));
      connectedToWiFi2 = true;

    } else {
      Serial.println("Failed to connect to any WiFi");
      send_data("wifi_name=N/A,signal=0,wifi=0,internet=0");
      connectedToWiFi2 = false;
    }

    // Stay connected
    while (WiFi.status() == WL_CONNECTED) {
      delay(10000);

      // If connected to WiFi 2, try to upgrade to WiFi 1 every 60 seconds
      static unsigned long lastCheck = 0;
      if (connectedToWiFi2 && millis() - lastCheck > 60000) {
        lastCheck = millis();
        Serial.println("Checking again for WiFi 1...");
        if (wifi1_ssid != "" && tryConnect(wifi1_ssid.c_str(), wifi1_pass.c_str())) {
          Serial.println("Switched to WiFi 1");
          signalLevel = getSignalLevel(WiFi.RSSI());
          hasInternet = testInternet();
          send_data("wifi_name=" + wifi1_ssid + ",wifi=1,signal=" + String(signalLevel) + ",internet=" + String(hasInternet ? 1 : 0));
          connectedToWiFi2 = false;
        }
      }
    }

    delay(5000);  // Retry after disconnect
  }
}

// ========== Your existing setup() extended ==========
void setup() {
  delay(5000);
  MySerial.begin(115200, SERIAL_8N1, 16, 17);
  send_data("wifi=0,internet=-,wifi_name=N/A,signal=0,pump_status=0,");

  Serial.begin(9600);
  analogReadResolution(12);

  SPI.begin();
  rfid.PCD_Init();

  pinMode(manual_override, INPUT);
  pinMode(pump_relay, OUTPUT);
  prefs.begin("wifi", true);
  ml_per_pulse = prefs.getString("mlperpulse", "10").toFloat();
  rands_per_liter = prefs.getString("rpl", "20.33").toFloat();
  Unit_ID = prefs.getString("pump_id", Unit_ID);  // override default if saved
  prefs.end();
  send_data("mlperpulse=" + String(ml_per_pulse) + ",");
  send_data("rpl=" + String(rands_per_liter) + ",");
  send_data("pump_id=" + Unit_ID);

  xTaskCreatePinnedToCore(rfid_handler, "rfid_read", 4096, NULL, 1, &rfid_read, 0);
  xTaskCreatePinnedToCore(pump_handler, "pump_algorithm", 4096, NULL, 1, &pump_algorithm, 1);
  xTaskCreatePinnedToCore(manual_override_handler, "manual_override_task", 4096, NULL, 1, &manual_override_task, 0);
  xTaskCreatePinnedToCore(wifiManagerTask, "wifi_connect", 4096, NULL, 1, &wifi_manager_task, 1);
  xTaskCreatePinnedToCore(wifiConfigTask, "wifi_config", 4096, NULL, 1, &wifi_config_task, 0);

  attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), increase, FALLING);

  getArduinoSettings();
}

// Liquid Counter
bool liquid_counter(int manual_over = 1) {
  attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), increase, FALLING);
  pulse = 0;
  old_pulse = 0;
  flow_counter = 0;

  unsigned long currentMillis;
  unsigned long previousMillis = millis();

  while (flow_counter <= time_out_s) {
    currentMillis = millis();
    if (currentMillis - previousMillis >= 1000) {
      previousMillis = currentMillis;
      pulse_dif = pulse - old_pulse;
      old_pulse = pulse;
      if (manual_over == 1) {
        if (pulse_dif < 1) {
          Serial.println(flow_counter);
          flow_counter++;
          // check this section SJ
        } else {
          flow_counter = 0;
          Serial.println("Pulse");
          Serial.println(pulse);
        }
      } else {
        if (pulse_dif < pulse_diff_threshold) {
          Serial.println(flow_counter);
          flow_counter++;
        } else {
          flow_counter = 0;
        }
      }
    }
    delay(1);
  }

  detachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN));
  return true;
}

// tank senor measure ment
String measure() {
  long adcSum = 0;
  for (int i = 0; i < SAMPLE_COUNT; i++) {
    adcSum += analogRead(TANK_SENSPOR_PIN);
    delay(2);
  }

  float adcAvg = adcSum / (float)SAMPLE_COUNT;
  float voltage = adcAvg * VREF / ADC_MAX;
  float rawCurrent = voltage / (R_SENSE / 1000.0);
  smoothedCurrent = ALPHA * rawCurrent + (1.0 - ALPHA) * smoothedCurrent;

  float depth_mm = (smoothedCurrent - MA_EMPTY) * mm_per_mA;
  if (depth_mm < 0) depth_mm = 0.0;

  Serial.print("ADC(avg): ");
  Serial.print(adcAvg, 1);
  Serial.print(" | Voltage: ");
  Serial.print(voltage, 3);
  Serial.print(" V | Current(smoothed): ");
  Serial.print(smoothedCurrent, 2);
  Serial.print(" mA | Depth: ");
  Serial.print(depth_mm, 2);
  Serial.println(" mm");

  return String(depth_mm, 1);
}

void send_tank_level_details() {
  String distanceMm = measure();
  HTTPClient http;
  String url = "http://" + String(serverName) + "/fuel/inte.php?tank_level=" + Unit_ID + "&distance=" + distanceMm;
  http.begin(client, url);

  int httpResponseCode = http.GET();
  if (httpResponseCode > 0) {
    String response = http.getString();
    if (response.indexOf("ACCEPTED") > 0) {
      Serial.println("Tank level sent");
    }
  } else {
    Serial.println("Tank level send error");
  }

  http.end();
}

// Renamed function to send_override_details
bool send_override_details(unsigned long pulse_value, int type, String rfid_tag) {
  HTTPClient http;
  String url = "http://" + String(serverName) + "/fuel/inte.php?unit_id=" + Unit_ID + "&pulse=" + String(pulse_value) + "&type=" + String(type) + "&rfid_tag=" + rfid_tag;
  http.begin(client, url);

  int httpResponseCode = http.GET();
  if (httpResponseCode > 0) {
    String response = http.getString();
    Serial.println(response);
    if (response.indexOf("ACCEPTED") != -1) {
      Serial.println("Override request accepted");
      send_data(",internet=1,");
      http.end();
      return true;
    }
  }

  Serial.println("Error: Failed to send override.");
  send_data(",internet=0,");

  http.end();
  return false;
}

void try_send_stored_overrides() {
  prefs.begin(PREFS_NAMESPACE, false);

  for (int i = 0; i < MAX_PENDING_OVERRIDES; i++) {
    String key = override_key(i);
    if (prefs.isKey(key.c_str())) {
      // Retrieve the stored string
      String stored_data = prefs.getString(key.c_str());

      // Deserialize the string back into an OverrideEntry struct
      OverrideEntry stored_entry = deserialize_entry(stored_data);

      Serial.println("Trying to send stored entry: " + stored_data);

      // Pass the individual components from the struct
      if (send_override_details(stored_entry.timestamp, stored_entry.type, stored_entry.rfid_tag)) {
        prefs.remove(key.c_str());
        Serial.println("Sent and removed stored entry.");
        send_data(",internet=1,");
      } else {
        Serial.println("Failed to send stored entry.");
        send_data(",internet=0,");
        break;  // Stop trying if a send fails
      }
    }
  }
  prefs.end();
}


void getArduinoSettings() {
  HTTPClient http;
  String url = "http://" + String(serverName) + "/fuel/inte.php?settings=" + Unit_ID;
  http.begin(client, url);

  int httpResponseCode = http.GET();
  if (httpResponseCode > 0) {
    String response = http.getString();
    if (response.indexOf("AUTHORISED") > 0) {
      time_out_s = response.substring(response.indexOf("t:") + 2, response.indexOf(":t")).toInt();
      send_data("internet=1,");
      interent = 1;
      Serial.println("Settings received. TIME OUT S: " + String(time_out_s));
    } else {
      Serial.println("Error: Unable to retrieve settings");
      send_data("internet=0,");
      interent = 0;
    }
  } else {
    Serial.print("Error on HTTP request: ");
    Serial.println(httpResponseCode);
    send_data("internet=0,");
    interent = 0;
    send_data(String(httpResponseCode));
  }
  http.end();
}

void pump_on() {
  send_data(",pump_status=1,pulses=" + String(pulse) + ",");
  delay(500);
  Serial.println("pump on");
  digitalWrite(pump_relay, HIGH);
  delay_timer(1000);
  pump_status = 1;
}

void pump_off() {
  delay_timer(500);
  send_data(",pump_status=0,");
  Serial.println("pump off");
  digitalWrite(pump_relay, LOW);
  delay_timer(1000);
  M_O_status = 0;
  pump_status = 0;
}

void delay_timer(int milis) {
  vTaskDelay(milis / portTICK_PERIOD_MS);
}

void loop() {
  delay(5000);
  try_send_stored_overrides();
  delay(5000);
  send_tank_level_details();
}

void trigger_pump_handler() {
  pump_status = 1;
}

// Task 1
void rfid_handler(void* pvParameters) {
  Serial.println("RFID Task running on Core 0");
  for (;;) {
    if (pump_status == 0 && M_O_status != 1) {
      Serial.println("Scan RFID tag...");
      if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) {
        unsigned long uidDecimal = 0;
        for (byte i = 0; i < rfid.uid.size; i++) {
          uidDecimal = (uidDecimal << 8) | rfid.uid.uidByte[i];
        }

        Serial.print("Card UID (Decimal): ");
        Serial.println(uidDecimal);

        String uidStr = String(uidDecimal);
        String searchStr = "," + uidStr + ",";
        String tagsWithCommas = "," + tags + ",";

        if (tagsWithCommas.indexOf(searchStr) != -1) {
          current_tran_type = "TAG";
          current_rfid_tag = uidStr; // Capture the RFID tag here
          send_data(",method=0,");
          delay_timer(2000);
          trigger_pump_handler();
        } else {
          send_data(",method=1,");
        }

        rfid.PICC_HaltA();
        rfid.PCD_StopCrypto1();
      }
    } else {
      Serial.println("RFID Stopped...");
    }
    delay_timer(150);  // Prevent watchdog reset
  }
}

// Task 2
void pump_handler(void* pvParameters) {
  Serial.println("Pump Task running on Core 1");
  for (;;) {
    if (pump_status == 1 && M_O_status == 0) {
      delay_timer(2000);
      pump_on();
      pulse = 0;
      old_pulse = 0;
      flow_counter = 0;
      unsigned long currentMillis;
      unsigned long previousMillis = millis();
      while (flow_counter <= time_out_s) {
        currentMillis = millis();
        if (currentMillis - previousMillis >= 500) {
          previousMillis = currentMillis;
          pulse_dif = pulse - old_pulse;
          old_pulse = pulse;
          if (currentMillis - lastRunTime >= interval) {
            lastRunTime = currentMillis;
            send_data(",pulses=" + String(pulse) + ",");
          }
          if (pulse_dif < 1) {
            Serial.println(flow_counter);
            flow_counter++;
            timer = time_out_s - flow_counter;
            send_data(",timer=" + String(timer) + ",");
            // check this section SJ
          } else {
            timer = time_out_s;
            flow_counter = 0;
            Serial.println("Pulse:");
            Serial.println(pulse);
          }
        }
      }
      pump_off();
      
      // Save the event data
      OverrideEntry entry;
      entry.timestamp = pulse; // Save the pulse value in the timestamp field
      entry.type = 0; // 0 for TAG event
      entry.rfid_tag = current_rfid_tag;
      save_to_storage(entry);

      delay_timer(100);
    }
    delay_timer(250);
  }
}

// Task 3
void manual_override_handler(void* pvParameters) {
  Serial.println("Manual Override Task running on Core 0");
  for (;;) {
    if (pump_status == 0) {
      if (digitalRead(manual_override) == HIGH) {
        pulse = 0;
        Serial.println("Manual Override triggered");
        M_O_status = 1;
        send_data(",method=3,pulses=" + String(pulse) + ",");
        delay_timer(4000);
        pump_on();
        int index = 0;
        while (digitalRead(manual_override) == HIGH) {
          delay_timer(500);
          send_data(",pulses=" + String(pulse) + ",");
        }
        delay_timer(500);
        pump_off();
        
        // Save the manual override event data
        OverrideEntry entry;
        entry.timestamp = pulse; // Save pulse value
        entry.type = 1; // 1 for MANUAL OVERRIDE
        entry.rfid_tag = "0"; // No tag for manual override
        save_to_storage(entry);

        Serial.println(pulse);
      }

    } else {
      Serial.println("Manual Override check Stopped...");
    }
    delay_timer(1000);
  }
}