#include <Arduino.h>
#include <ArduinoJson.h>
#include <WiFiClient.h>
#include <HTTPClient.h>

String byteArrayAsHexString(byte* buffer, byte bufferSize) {
	String s = "";
	for (byte i = 0; i < bufferSize; i++) {
		s += (buffer[i] < 0x10 ? "0" : "");
		s += String(buffer[i], HEX);
	}
	return s;
}

String CentsToEuros(long cents){
	char euros[16];
	sprintf(euros, "%s%ld,%02ld", cents < 0 ? "-" : "", abs(cents / 100), abs(cents % 100));
	return euros;
}

unsigned long cooldownSecondsRemaining(unsigned long timeout, unsigned long timer) {
	return (timeout - (millis() - timer)) / 1000 + 1;
}

String getIdempotencyKey(){
	const int len = 16;
	byte buf[len];
	esp_fill_random(buf, len);
	return byteArrayAsHexString(buf, len);
}

String splitString(const String& data, char separator, int index) {
	unsigned int maxIndex = data.length() - 1;
	int strIndex[] = {0, -1};
	int found = 0;

	for (int i = 0; i <= maxIndex && found <= index; i++) {
		if (data.charAt(i) == separator || i == maxIndex) {
			found++;
			strIndex[0] = strIndex[1] + 1;
			strIndex[1] = (i == maxIndex) ? i + 1 : i;
		}
	}
	return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}


String cardLink(WiFiClient* wifi, HTTPClient* http, const String& apiUrl, const String& cardId, const String& reader) {
	String finalRequestUrl = apiUrl + "/api/card/" + cardId + "/link" + "?reader=" + reader;
	http->begin(*wifi, finalRequestUrl.c_str());
	http->addHeader("Content-Type", "application/json");
	int httpResponseCode = http->PUT("");
	if (httpResponseCode == 304)
		return "E:Already registered.";
	if (httpResponseCode == 200 || httpResponseCode == 404) {
		String payload = http->getString();
		http->end();
		Serial.println(payload);
		StaticJsonDocument<256> json;
		DeserializationError error = deserializeJson(json, payload.c_str());

		if (error) {
			Serial.println(error.c_str());
			return String("E:JsonError:") + httpResponseCode;
		}

		const char* status = json["status"];

		if (strcmp(status, "success") != 0) {
			const char* message = json["message"];
			return String("E:") + message;
		}

		JsonObject data = json["data"];
		const char* nickname = data["nickname"];
		long balance = data["balance"];

		return String("S:") + nickname + ":" + CentsToEuros(balance);
	}
	http->end();
	if (httpResponseCode > 0) {
		return String("E:HTTP Error ") + httpResponseCode;
	}
	return String("E:Internal Error ") + httpResponseCode;
}

String cardBalance(WiFiClient* wifi, HTTPClient* http, const String& apiUrl, const String& cardId, const String& reader) {
	String finalRequestUrl = apiUrl + "/api/card/" + cardId + "/balance" + "?reader=" + reader;
	http->begin(*wifi, finalRequestUrl.c_str());
	http->addHeader("Content-Type", "application/json");
	int httpResponseCode = http->GET();
	if (httpResponseCode == 200 || httpResponseCode == 404) {
		String payload = http->getString();
		http->end();
		Serial.println(payload);
		StaticJsonDocument<256> json;
		DeserializationError error = deserializeJson(json, payload.c_str());

		if (error) {
			Serial.println(error.c_str());
			return "E:JsonError";
		}

		const char* status = json["status"];

		if (strcmp(status, "success") != 0) {
			const char* message = json["message"];
			return String("E:") + message;
		}

		JsonObject data = json["data"];
		const char* nickname = data["nickname"];
		long balance = data["balance"];

		return String("S:") + nickname + ":" + CentsToEuros(balance);
	}
	http->end();
	if (httpResponseCode > 0) {
		return String("E:HTTP Error ") + httpResponseCode;
	}
	return String("E:Internal Error ") + httpResponseCode;
}

String cardTransaction(WiFiClient* wifi, HTTPClient* http, const String& apiUrl, const String& cardId, const String& amount, const String& reader) {
	String idempotencyKey = getIdempotencyKey();
	String finalRequestUrl = apiUrl + "/api/card/" + cardId + "/transaction/" + idempotencyKey + "?amount=" + amount + "&reader=" + reader;
	http->begin(*wifi, finalRequestUrl.c_str());
	http->addHeader("Content-Type", "application/json");
	int httpResponseCode = http->PUT("");
	if (httpResponseCode == 200 || httpResponseCode == 404 || httpResponseCode == 412) {
		String payload = http->getString();
		http->end();
		Serial.println(payload);
		StaticJsonDocument<256> json;
		DeserializationError error = deserializeJson(json, payload.c_str());

		if (error) {
			return "E:JsonError";
		}

		const char* status = json["status"];

		if (strcmp(status, "success") != 0) {
			const char* message = json["message"];
			return String("E:") + message;
		}

		JsonObject data = json["data"];
		const char* nickname = data["nickname"];
		long balance = data["balance"];

		return String("S:") + nickname + ":" + CentsToEuros(balance);
	}
	http->end();
	if (httpResponseCode > 0) {
		return String("E:HTTP Error ") + httpResponseCode;
	}
	return String("E:Internal Error ") + httpResponseCode;
}

String cardIdDisplay(const String& id){
	if (id.length() <= 14)
		return "Card #" + id;
	else if (id.length() <= 19)
		return "#" + id;
	else
		return "#" + id.substring(0, 16) + "...";
}
