#include <WiFiNINA.h>
#include <Wire.h>
#include "bsec.h"
#include <ArduinoHttpClient.h>
#include <WiFiUdp.h>
#include <NTPClient.h>

// WLAN-Zugangsdaten
#define SSID ""
#define PASSWORT ""

// API-Konfiguration
#define API_HOST ""
#define API_PORT 8080
#define API_ENDPOINT "/sensors/push-data/secure"
#define CLIENT_ID ""
#define API_TOKEN ""

const String ALPHABET = "";
const String KEY      = "";

// Sensor & Netzwerk
Bsec iaqSensor;
WiFiClient wifi;
HttpClient client = HttpClient(wifi, API_HOST, API_PORT);

// NTP-Client
WiFiUDP ntpUDP;
const long utcOffsetInSeconds = 0;
NTPClient timeClient(ntpUDP, "pool.ntp.org", utcOffsetInSeconds);

// Sendeintervall
unsigned long sendInterval = 30000;

// Fehlercode per LED ausgeben (Morse-artig)
void errorBlink(int code) {
  while (true) {
    for (int i = 0; i < code; i++) {
      digitalWrite(LED_BUILTIN, HIGH);
      delay(150);
      digitalWrite(LED_BUILTIN, LOW);
      delay(150);
    }
    delay(1000); // Pause zwischen Zyklen
  }
}

// Werte begrenzen
float clampValue(float val) {
  if (val >= 1000.0) return 999.999;
  if (val <= -1000.0) return -999.999;
  return val;
}

// ISO-Zeitstempel generieren
String getTimestamp() {
  timeClient.update();
  unsigned long epochTime = timeClient.getEpochTime();
  int year = 1970;
  unsigned long seconds = epochTime;

  while (true) {
    bool leap = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
    int daysInYear = leap ? 366 : 365;
    if (seconds >= daysInYear * 86400UL) {
      seconds -= daysInYear * 86400UL;
      year++;
    } else {
      break;
    }
  }

  int month = 1;
  const int daysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};
  while (month <= 12) {
    int dim = daysInMonth[month - 1];
    if (month == 2 && (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))) {
      dim = 29;
    }
    if (seconds >= dim * 86400UL) {
      seconds -= dim * 86400UL;
      month++;
    } else {
      break;
    }
  }

  int day = seconds / 86400UL + 1;
  seconds = seconds % 86400UL;
  int hour = seconds / 3600UL;
  seconds = seconds % 3600UL;
  int minute = seconds / 60UL;
  int second = seconds % 60UL;

  char buf[30];
  sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second);
  return String(buf);
}

// API-Health-Check
bool checkApiHealth() {
  HttpClient healthClient = HttpClient(wifi, API_HOST, API_PORT);
  healthClient.get("/health");
  int statusCode = healthClient.responseStatusCode();
  healthClient.stop();
  return statusCode == 200;
}

// Funktion zum Substituieren (Verschlüsseln) eines Strings
String substituteString(String input, String alphabet, String key) {
  input.toUpperCase();
  String output = "";
  for (int i = 0; i < input.length(); i++) {
    char c = input.charAt(i);
    int index = alphabet.indexOf(c);
    if (index == -1) { // Zeichen nicht im Alphabet, unverändert belassen (z.B. Leerzeichen?)
      output += c;
    } else {
      output += key.charAt(index);
    }
  }
  return output;
}

void setup() {

  Serial.begin(9600);
  while (!Serial);
  Serial.println("Start");

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);

  // WLAN verbinden mit Timeout
  WiFi.begin(SSID, PASSWORT);
  unsigned long startAttemptTime = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) {
    delay(500);
  }
  if (WiFi.status() != WL_CONNECTED) {
    errorBlink(1); // Fehlercode 1: WLAN-Fehler
  }

  // API-Healthcheck
  if (!checkApiHealth()) {
    errorBlink(4); // Fehlercode 4: API nicht erreichbar
  }

  Wire.begin();

  // Automatische Sensor-Erkennung
  byte sensorAddress = 0;
  byte possibleAddresses[] = {0x76, 0x77};
  bool sensorFound = false;
  for (int i = 0; i < 2; i++) {
    byte addr = possibleAddresses[i];
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      sensorAddress = addr;
      sensorFound = true;
      break;
    }
  }

  if (!sensorFound) {
    errorBlink(2); // Fehlercode 2: Sensor nicht gefunden
  }

  iaqSensor.begin(sensorAddress, Wire);
  if (iaqSensor.bsecStatus != BSEC_OK) {
    errorBlink(3); // Fehlercode 3: Sensor-Init fehlgeschlagen
  }

  bsec_virtual_sensor_t sensorList[] = {
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_RAW_GAS
  };
  iaqSensor.updateSubscription(sensorList, 5, BSEC_SAMPLE_RATE_LP);

  timeClient.begin();
  while (!timeClient.update()) {
    timeClient.forceUpdate();
  }

  digitalWrite(LED_BUILTIN, HIGH); // Alles bereit – LED dauerhaft an
}

void loop() {
  if (iaqSensor.run()) {
    // Kurzes LED-Blink zur Messanzeige
    digitalWrite(LED_BUILTIN, LOW);
    delay(100);
    digitalWrite(LED_BUILTIN, HIGH);

    float temperature = clampValue(iaqSensor.temperature);
    float humidity = clampValue(iaqSensor.humidity);
    float voc = clampValue(iaqSensor.iaq);
    float gas = clampValue(iaqSensor.gasResistance / 1000.0);
    float pressure = iaqSensor.pressure / 100.0;
    String timestamp = getTimestamp();

    // Werte in Strings mit z.B. 3 Dezimalstellen
    String temperatureStr = String(temperature, 3);
    String humidityStr = String(humidity, 3);
    String vocStr = String(voc, 3);
    String gasStr = String(gas, 3);
    String pressureStr = String(pressure, 3);

    // Alle Strings verschlüsseln (substituieren)
    String encryptedTimestamp = substituteString(timestamp, ALPHABET, KEY);
    String encryptedTemperature = substituteString(temperatureStr, ALPHABET, KEY);
    String encryptedHumidity = substituteString(humidityStr, ALPHABET, KEY);
    String encryptedVoc = substituteString(vocStr, ALPHABET, KEY);
    String encryptedGas = substituteString(gasStr, ALPHABET, KEY);
    String encryptedPressure = substituteString(pressureStr, ALPHABET, KEY);

    // JSON-Payload mit verschlüsselten Strings
    String payload = "{";
    payload += "\"timestamp\": \"" + encryptedTimestamp + "\",";
    payload += "\"temperature\": \"" + encryptedTemperature + "\",";
    payload += "\"humidity\": \"" + encryptedHumidity + "\",";
    payload += "\"pressure\": \"" + encryptedPressure + "\",";
    payload += "\"voc\": \"" + encryptedVoc + "\",";
    payload += "\"gas\": \"" + encryptedGas + "\"";
    payload += "}";

    // Sende Request
    String fullPath = String(API_ENDPOINT) + "?client=" + CLIENT_ID;
    client.beginRequest();
    client.post(fullPath);
    client.sendHeader("Content-Type", "application/json");
    client.sendHeader("token", API_TOKEN);
    client.sendHeader("Content-Length", payload.length());
    client.beginBody();
    client.print(payload);
    client.endRequest();

    Serial.println(payload);
    Serial.println(fullPath);

    int statusCode = client.responseStatusCode();
    String response = client.responseBody();
    Serial.println(statusCode);
    Serial.println(response);

  }

  delay(sendInterval);
}
