#include <WiFi.h>
#include <WebServer.h>
#include <WiFiClientSecure.h>
#include <Adafruit_NeoPixel.h>
#include <PubSubClient.h>

// LED strip configuration
#define LED_PIN     5
#define NUM_LEDS    12

// port for HiveMQ
#define MQTT_PORT   8883

// WiFi Credentials
const char* ssid = "your_wifi_username";
const char* password = "your_wifi_password";

// MQTT broker details
const char* mqtt_server = "your_HiveMQ_server_TLS_url";
const char* mqtt_topic = "your_HiveMQ_topic";
const char* mqtt_user = "your_HiveMQ_user";
const char* mqtt_password = "your_HiveMQ_pass";

// Static IP configuration (change depending on your network setup)
IPAddress local_IP(192, 168, 1, 184); 
IPAddress gateway(192, 168, 1, 1); 
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8); 
IPAddress secondaryDNS(8, 8, 4, 4); 

// Initialize objects
WiFiClientSecure espClient;
PubSubClient client(espClient);
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);
WebServer server(80); // HTTP server on port 80


// Variables (can add scenarios to initiate this on start)
int previousHealthGrade = 6;
String healthGradeStr = "6";


// Function to print network information
void printNetworkInfo() {
    if (WiFi.status() == WL_CONNECTED) {
        Serial.print("[*] Network information for ");
        Serial.println(ssid);
        Serial.println("[+] BSSID: " + WiFi.BSSIDstr());
        Serial.print("[+] Gateway IP: ");
        Serial.println(WiFi.gatewayIP());
        Serial.print("[+] Subnet Mask: ");
        Serial.println(WiFi.subnetMask());
        Serial.println("[+] RSSI: " + String(WiFi.RSSI()) + " dB");
        Serial.print("[+] ESP32 IP: ");
        Serial.println(WiFi.localIP());
    }
}

// Function to reconnect to MQTT broker
void reconnectMQTT() {
    while (!client.connected()) {
        Serial.print("Attempting MQTT connection...");
        if (client.connect("ESP32Client", mqtt_user, mqtt_password)) {
            Serial.println("connected");
        } else {
            Serial.print("failed, rc=");
            Serial.print(client.state());
            Serial.println(" try again in 5 seconds");
            delay(5000);
        }
    }
}

// Function to set LED light based on health grade
void setLEDLight() {
    int healthGrade = healthGradeStr.toInt();
    int red, green, blue;

    if (healthGrade <= 3) {
        red = 200;
        green = 0;
        blue = 10;
    } else if (healthGrade <= 6) {
        red = 200;
        green = 165;
        blue = 10;
    } else {
        red = 0;
        green = 200;
        blue = 10;
    }

    // Blink LEDs three times
    for (int j = 0; j < 3; j++) {
        for (int i = 0; i < NUM_LEDS; i++) {
            strip.setPixelColor(i, strip.Color(red, green, blue));
        }
        strip.show();
        delay(500);

        for (int i = 0; i < NUM_LEDS; i++) {
            strip.setPixelColor(i, 0);
        }
        strip.show();
        delay(500);
    }

    // Set LEDs to the final color state
    for (int i = 0; i < NUM_LEDS; i++) {
        strip.setPixelColor(i, strip.Color(red, green, blue));
    }
    strip.show();
}

// Function to handle wake-up event
void handleWakeUp() {
    if (server.hasArg("healthGrade")) {
        healthGradeStr = server.arg("healthGrade");
        Serial.println("Received health grade: " + healthGradeStr);
        server.send(200, "text/plain", "OK");
    }
}

// Function to handle webhook events
void handleWebhook() {
    if (server.hasArg("healthGrade")) {
        healthGradeStr = server.arg("healthGrade");
        int healthGrade = healthGradeStr.toInt();

        Serial.println("Received health grade: " + healthGradeStr);

        String message;
        if (healthGrade == previousHealthGrade) {
            Serial.println("Unchanged");
            message = "Health grade is unaffected.";
        } else {
            float percentageChange = 0;
            String changeType;

            if (previousHealthGrade != 0) {
                percentageChange = ((float)(healthGrade - previousHealthGrade) / previousHealthGrade) * 100;
                if (percentageChange > 0) {
                    changeType = "Improved by ";
                } else if (percentageChange < 0) {
                    changeType = "Degraded by ";
                    percentageChange = -percentageChange;
                }
            } else {
                changeType = "Initial health grade set to ";
            }

            message = "Health grade has changed to " + healthGradeStr;
            if (previousHealthGrade != 0) {
                message += ". " + changeType + String(percentageChange, 2) + "%.";
            }

            previousHealthGrade = healthGrade;
            client.publish(mqtt_topic, message.c_str());
            setLEDLight();
        }

        server.send(200, "text/plain", "OK");
    }
}

// Function to trigger Make.com webhook
void triggerMakeWebhook() {
    Serial.println("Triggering Make.com webhook...");

    if (espClient.connect("hook.eu2.make.com", 443)) {
        Serial.println("Connected to Make.com webhook server!");
        espClient.println("POST /uu2phwnevq01efparibekbv1t2otmc39 HTTP/1.1");
        espClient.println("Host: hook.eu2.make.com");
        espClient.println("User-Agent: ESP32");
        espClient.println("Connection: close");
        espClient.println("Content-Length: 0");
        espClient.println();

        while (espClient.connected() || espClient.available()) {
            if (espClient.available()) {
                String line = espClient.readStringUntil('\n');
                Serial.println(line);
            }
        }
        espClient.stop();
    } else {
        Serial.println("Connection to Make.com webhook server failed!");
    }
}

// Function to publish MQTT notification
void publishNotification() {
    if (!client.connected()) {
        reconnectMQTT();
    }
    client.loop();

    String message = "Health grade has changed to " + healthGradeStr;
    client.publish(mqtt_topic, message.c_str());
    Serial.println("Published message to MQTT: " + message);
}

// Setup function
void setup() {
    Serial.begin(115200);
    Serial.println("Starting...");

    strip.begin();
    setLEDLight();

    if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
        Serial.println("STA Failed to configure");
    }

    WiFi.begin(ssid, password);
    Serial.println("Connecting to WiFi...");

    unsigned long startAttemptTime = millis();
    const unsigned long wifiTimeout = 20000; // 20 seconds timeout

    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < wifiTimeout) {
        delay(1000);
        Serial.print(".");
    }

    if (WiFi.status() == WL_CONNECTED) {
        printNetworkInfo();
        espClient.setInsecure();

        client.setServer(mqtt_server, MQTT_PORT);
        reconnectMQTT();

        server.on("/webhook", HTTP_POST, handleWebhook);
        server.begin();

        Serial.println("Server started...");
    } else {
        Serial.println("\nFailed to connect to WiFi, restarting...");
        ESP.restart();
    }
}

// Loop function
void loop() {
    server.handleClient();
    if (!client.connected()) {
        reconnectMQTT();
    }
}
