#include "discordtypes.h"
#include "http.h"
#include "config.h"
#include <windows.h>
#include <winnt.h>
#include <winternl.h>
#include <string.h>
#include <cjson/cJSON.h>
#include "ws.h"
#include "globals.h"
#include "hmap/hashmap.h"
hmap GuildsTable = NULL;
hmap ChannelsTable = NULL;
DiscordChannel *PrivateChannels = NULL;
int PrivateChannelCount = 0;
static uint64_t GuildHash(const void *item, uint64_t seed0, uint64_t seed1) {
	const DiscordGuild *e = item;
	return hashmap_sip(e->id, strlen(e->id), seed0, seed1);
}
static int GuildCompare(const void *a, const void *b, void *udata) {
	return strcmp(((DiscordGuild *)a)->id, ((DiscordGuild *)b)->id);
}
static void GuildFree(void *item) {}

static uint64_t ChannelHash(const void *item, uint64_t seed0, uint64_t seed1) {
	const DiscordChannel *e = item;
	return hashmap_sip(e->id, strlen(e->id), seed0, seed1);
}
static int ChannelCompare(const void *a, const void *b, void *udata) {
	return strcmp(((DiscordChannel *)a)->id, ((DiscordChannel *)b)->id);
}
static void ChannelFree(void *item) {}
extern void onDiscordGuildLoad(DiscordGuild *guild, char *id);
extern void onDiscordUpdatedGuildReadState(DiscordGuild gld);
int DiscordSendMessage(const char *channelID, const char *content) {
	cJSON *payload = cJSON_CreateObject();
	cJSON *name = cJSON_CreateString(content);
	cJSON_AddItemToObject(payload, "content", name);
	char *endpoint = malloc(28 + strlen(channelID));
	endpoint[0] = '\0';
	strcat(endpoint, "/api/v9/channels/");
	strcat(endpoint, channelID);
	strcat(endpoint, "/messages");
	char *forNothingGng;
	char *payld = cJSON_Print(payload);
	long resplen;
	char headers[17 + strlen(token) + 1];
	wsprintf(headers, "Authorization: %s\r\n", token);
	SendShortHTTPReq("discord.com", "POST", endpoint, headers, user_agent,
					 "application/json", payld, strlen(payld), &forNothingGng,
					 &resplen);
	free(payld);
	cJSON_free(payload);
	free(forNothingGng);
}
int DiscordListGuildChannels(char *guildID, DiscordChannel **out) {
	DiscordGuild *gld;
	DiscordGuild search;
	search.id = guildID;
	gld = hashmap_get(GuildsTable, &search);
	DiscordChannel *arr1 = (RtlAllocateHeap(
		GetProcessHeap(), 0, sizeof(DiscordChannel) * gld->ChannelCount));
	memset(arr1, 0, sizeof(DiscordChannel) * gld->ChannelCount);
	*out = arr1;
	for (int i = 0; i < gld->ChannelCount; i++) {
		(*out)[i] = gld->channels[i];
	}
	return gld->ChannelCount;
}
char *DiscordFetchTmpPfp(char *userID, char *hash) {
	char *endpoint = malloc(80);
	wsprintfA(endpoint, "/avatars/%s/%s.png?size=512", userID, hash);
	char *HTTPResp;
	long resplen;
	char headers[17 + strlen(token) + 1];
	wsprintf(headers, "Authorization: %s\r\n", token);
	SendShortHTTPReq("cdn.discordapp.com", "GET", endpoint, headers, user_agent,
					 "application/json", NULL, 0, &HTTPResp, &resplen);
	free(endpoint);

	char tempPath[MAX_PATH];

	DWORD len = GetTempPathA(MAX_PATH, tempPath);
	char *path = malloc(MAX_PATH + strlen(hash) + 1);
	wsprintfA(path, "%s%s.png", tempPath, hash);
	HANDLE file = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
							  CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	DWORD written;
	if (file == INVALID_HANDLE_VALUE) {
		MessageBoxA(NULL,
					"oof, failed opening the file to save the tmp pfp file!",
					"Backcord - DiscordFetchTmpPfp", 0);
		free(path);
		return NULL;
	}
	WriteFile(file, HTTPResp, resplen, &written, NULL);
	CloseHandle(file);
	free(HTTPResp);
	return path;
}
char *DiscordFetchTmpGuildIcon(char *guildID, char *hash) {
	char *endpoint = malloc(80);
	wsprintfA(endpoint, "/icons/%s/%s.png?size=512", guildID, hash);
	char *HTTPResp;
	long resplen;
	char headers[17 + strlen(token) + 1];
	wsprintf(headers, "Authorization: %s\r\n", token);
	SendShortHTTPReq("cdn.discordapp.com", "GET", endpoint, headers, user_agent,
					 "application/json", NULL, 0, &HTTPResp, &resplen);
	free(endpoint);

	char tempPath[MAX_PATH];

	DWORD len = GetTempPathA(MAX_PATH, tempPath);
	char *path = malloc(MAX_PATH + strlen(hash) + 1);
	wsprintfA(path, "%s%s.png", tempPath, hash);
	HANDLE file = CreateFileA(path, GENERIC_READ | GENERIC_WRITE, 0, NULL,
							  CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	DWORD written;
	if (file == INVALID_HANDLE_VALUE) {
		MessageBoxA(NULL,
					"oof, failed opening the file to save the tmp icon file!",
					"Backcord - Guild Icon Retrieval", 0);
		free(path);
		return NULL;
	}
	WriteFile(file, HTTPResp, resplen, &written, NULL);
	CloseHandle(file);
	free(HTTPResp);
	return path;
}
int DiscordGetChannelHistory(const char *channelID, unsigned int amount,
							 DiscordMessage **msgs) {
	cJSON *payload = cJSON_CreateObject();
	char *endpoint = malloc(38 + strlen(channelID));
	memset((endpoint), 0, (27 + strlen(channelID)));
	strcat(endpoint, "/api/v9/channels/");
	strcat(endpoint, channelID);
	strcat(endpoint, "/messages?limit=");
	char *amountstr = malloc(3 * sizeof(char));
	wsprintf(amountstr, "%i", amount);
	strcat(endpoint, amountstr);
	free(amountstr);
	char *HTTPResp;
	long resplen;

	char headers[17 + strlen(token) + 1];
	wsprintf(headers, "Authorization: %s\r\n", token);

	SendShortHTTPReq("discord.com", "GET", endpoint, headers, user_agent,
					 "application/json", NULL, 0, &HTTPResp, &resplen);
	cJSON *resp = cJSON_Parse(HTTPResp);
	if (!resp)
		return 0;
	if (!cJSON_IsArray(resp))
		return 0;
	*msgs = malloc(sizeof(DiscordMessage) * amount);
	for (int i = 0; i < cJSON_GetArraySize(resp); i++) {
		cJSON *item = cJSON_GetArrayItem(resp, i);

		(*msgs)[i].content = cJSON_GetObjectItem(item, "content")->valuestring;
		(*msgs)[i].channelID =
			cJSON_GetObjectItem(item, "channel_id")->valuestring;
		(*msgs)[i].id = cJSON_GetObjectItem(item, "id")->valuestring;
		(*msgs)[i].type = cJSON_GetObjectItem(item, "type")->valueint;
		cJSON *author = cJSON_GetObjectItem(item, "author");
		cJSON *dispname = cJSON_GetObjectItem(author, "global_name");
		(*msgs)[i].author.DisplayName = (dispname && cJSON_IsString(dispname))
											? strdup(dispname->valuestring)
											: NULL;

		cJSON *av = cJSON_GetObjectItem(author, "avatar");
		(*msgs)[i].author.avatar =
			(av && cJSON_IsString(av)) ? strdup(av->valuestring) : NULL;

		cJSON *ct = cJSON_GetObjectItem(item, "content");
		(*msgs)[i].content =
			(ct && cJSON_IsString(ct)) ? strdup(ct->valuestring) : strdup("");
		(*msgs)[i].author.Username =
			cJSON_GetObjectItem(author, "username")->valuestring;
		(*msgs)[i].author.DisplayName =
			cJSON_GetObjectItem(author, "global_name")->valuestring;
		(*msgs)[i].author.id = cJSON_GetObjectItem(author, "id")->valuestring;
		(*msgs)[i].author.avatar =
			cJSON_GetObjectItem(author, "avatar")->valuestring;
	}
	return cJSON_GetArraySize(resp);
	free(HTTPResp);
}
int ConnectGateway(const char *channelID, const char *content) {}
DiscordGuild *GetGuild(char *id) {
	DiscordGuild *g;
	DiscordGuild search;
	search.id = id;
	g = (DiscordGuild*)hashmap_get(GuildsTable, &search);
	return g;
}
int DiscordListPrivateChannels(DiscordChannel **out) {
	*out = PrivateChannels;
	return PrivateChannelCount;
}
void HandleREADY(cJSON *json) {
	cJSON *data = cJSON_GetObjectItem(json, "d");
	if (!cJSON_IsObject(data))
		return;

	cJSON *guilds = cJSON_GetObjectItem(data, "guilds");
	if (!cJSON_IsArray(guilds))
		return;

	int size = cJSON_GetArraySize(guilds);
	GuildsTable = hashmap_new(sizeof(DiscordGuild), 256, 0, 0, GuildHash,
							  GuildCompare, GuildFree, NULL);
	ChannelsTable = hashmap_new(sizeof(DiscordChannel), 256, 0, 0, ChannelHash,
							  ChannelCompare, ChannelFree, NULL);

	cJSON *privchannels = cJSON_GetObjectItem(data, "private_channels");
	PrivateChannelCount = cJSON_GetArraySize(privchannels);
	PrivateChannels = malloc(sizeof(DiscordChannel) * PrivateChannelCount);
	for (int j = 0; j < PrivateChannelCount; j++) {
		cJSON *ch = cJSON_GetArrayItem(privchannels, j);
		if (!cJSON_IsObject(ch))
			continue;

		cJSON *chName = cJSON_GetObjectItem(ch, "name");
		cJSON *chType = cJSON_GetObjectItem(ch, "type");
		cJSON *chId = cJSON_GetObjectItem(ch, "id");
		cJSON *chPid = cJSON_GetObjectItem(ch, "parent_id");
		cJSON *chRecp = cJSON_GetObjectItem(ch, "recipients");

		if (!cJSON_IsString(chId) || !cJSON_IsNumber(chType))
			continue;
			
		PrivateChannels[j].GuildID = NULL;
		PrivateChannels[j].name = chName ? strdup(chName->valuestring) : NULL;
		PrivateChannels[j].id = strdup(chId->valuestring);
		PrivateChannels[j].type = chType->valueint;
		PrivateChannels[j].parentID = (chPid && cJSON_IsString(chPid))
										  ? strdup(chPid->valuestring)
										  : NULL;
		PrivateChannels[j].receipents =
			malloc(cJSON_GetArraySize(chRecp) * sizeof(DiscordUser));
		for (int i = 0; i < cJSON_GetArraySize(chRecp); i++) {
			cJSON *recep = cJSON_GetArrayItem(chRecp, i);
			cJSON *recpAv = cJSON_GetObjectItem(recep, "avatar");
			PrivateChannels[j].receipents[i].avatar =
				(recpAv && cJSON_IsString(recpAv)) ? strdup(recpAv->valuestring)
												   : NULL;

			cJSON *recpUn = cJSON_GetObjectItem(recep, "username");
			PrivateChannels[j].receipents[i].Username =
				(recpUn && cJSON_IsString(recpUn)) ? strdup(recpUn->valuestring)
												   : NULL;

			cJSON *recpDn = cJSON_GetObjectItem(recep, "global_name");
			PrivateChannels[j].receipents[i].DisplayName =
				(recpDn && cJSON_IsString(recpDn)) ? strdup(recpDn->valuestring)
												   : NULL;

			cJSON *recpId = cJSON_GetObjectItem(recep, "id");
			PrivateChannels[j].receipents[i].id =
				(recpId && cJSON_IsString(recpId)) ? strdup(recpId->valuestring)
												   : NULL;
		}
	}

	for (int i = size - 1; i >= 0; i--) {
		cJSON *guild = cJSON_GetArrayItem(guilds, i);
		if (!cJSON_IsObject(guild))
			continue;

		cJSON *channels = cJSON_GetObjectItem(guild, "channels");
		int channelCount = cJSON_GetArraySize(channels);
		DiscordChannel *gchannels =
			malloc(sizeof(DiscordChannel) * channelCount);
		for (int j = 0; j < channelCount; j++) {
			cJSON *ch = cJSON_GetArrayItem(channels, j);
			if (!cJSON_IsObject(ch))
				continue;

			cJSON *chName = cJSON_GetObjectItem(ch, "name");
			cJSON *chType = cJSON_GetObjectItem(ch, "type");
			cJSON *chId = cJSON_GetObjectItem(ch, "id");
			cJSON *chPid = cJSON_GetObjectItem(ch, "parent_id");

			if (!cJSON_IsString(chName) || !cJSON_IsString(chId) ||
				!cJSON_IsNumber(chType))
				continue;

			gchannels[j].name = strdup(chName->valuestring);
			gchannels[j].id = strdup(chId->valuestring);
			gchannels[j].type = chType->valueint;
			gchannels[j].parentID = (chPid && cJSON_IsString(chPid))
										? strdup(chPid->valuestring)
										: NULL;
			gchannels[j].GuildID =
			strdup(cJSON_GetObjectItem(guild, "id")->valuestring);
			hashmap_set(ChannelsTable,&gchannels[j]);
		}
		char *name = cJSON_GetObjectItem(guild, "name")->valuestring;
		char *id = cJSON_GetObjectItem(guild, "id")->valuestring;
		DiscordGuild *g = malloc(sizeof(DiscordGuild));
		g->id = strdup(id);
		g->name = strdup(name);
		cJSON *icon = cJSON_GetObjectItem(guild, "icon");
		g->IconHash =
			(icon && cJSON_IsString(icon)) ? strdup(icon->valuestring) : NULL;
		g->ObtainedIconPath = NULL;
		g->channels = gchannels;
		g->ChannelCount = channelCount;
		g->MentionCount = 0;
		hashmap_set(GuildsTable, g);

		onDiscordGuildLoad(g, id);
	}

	cJSON *readstates = cJSON_GetObjectItem(data, "read_state");
	for (int i = 0; i < cJSON_GetArraySize(readstates); i++) {
		cJSON *readstate = cJSON_GetArrayItem(readstates, i);
			DiscordChannel chnprop;
			chnprop.id = cJSON_GetObjectItem(readstate, "id")->valuestring;
			DiscordChannel *che =
				(DiscordChannel *)hashmap_get(ChannelsTable, &chnprop);
			if (!che) {
				// bradar WHAT IS THIS
				printf("NOT FOUND: Channel %s\n", chnprop.id);
				continue;
			}
			che->ReadState.MentionCount =
				cJSON_GetObjectItem(readstate, "mention_count")
					? cJSON_GetObjectItem(readstate, "mention_count")->valueint
					: 0;
			if (cJSON_GetObjectItem(readstate, "mention_count")
					? cJSON_GetObjectItem(readstate, "mention_count")->valueint
					: 0) {
				if (che->GuildID) {
					DiscordGuild *gld = GetGuild(che->GuildID);
					MessageBoxA(NULL, gld->name, "AAAAAAAAAA", 0);
					if (gld) {
						gld->MentionCount += che->ReadState.MentionCount;
						onDiscordUpdatedGuildReadState(*gld);
					}
				}
			}
	}
}
extern void onDiscordReceiveMessage(DiscordMessage msg);
void HandleMessageCreate(cJSON *json) {
	cJSON *d = cJSON_GetObjectItem(json, "d");
	cJSON *author = cJSON_GetObjectItem(d, "author");
	DiscordMessage msg;
	msg.author.DisplayName =
		strdup(cJSON_GetObjectItem(author, "global_name")->valuestring);
	msg.author.id = strdup(cJSON_GetObjectItem(author, "id")->valuestring);
	msg.channelID = strdup(cJSON_GetObjectItem(d, "channel_id")->valuestring);
	msg.content = strdup(cJSON_GetObjectItem(d, "content")->valuestring);
	if (cJSON_GetObjectItem(author, "avatar")) {
		msg.author.avatar = cJSON_GetObjectItem(author, "avatar")->valuestring;
	}
	onDiscordReceiveMessage(msg);
}
void HandleDiscordDispatch(cJSON *json) {
	if (!cJSON_IsObject(json))
		return;
	cJSON *t = cJSON_GetObjectItem(json, "t");
	if (!t || !cJSON_IsString(t))
		return;
	if (strcmp(t->valuestring, "READY") == 0) {
		HandleREADY(json);
	}
	if (strcmp(t->valuestring, "MESSAGE_CREATE") == 0) {
		HandleMessageCreate(json);
	}
}

void CALLBACK HeartbeatProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent,
							DWORD dwTime) {
	SendWebSocket(GatewaySSL, "{\"op\": 1,\"d\": null}", 19, 0x81);
}
void WebSocketOnDataArrival(SSL *ssl, char *buffer, size_t length) {
	cJSON *json = cJSON_Parse(buffer);
	if (json == NULL) {
		if (strstr(buffer, "Authentication failed") != 0) {
			MessageBoxA(NULL,
						"The provided token may be outdated, there was an "
						"issue in authentication!",
						"Backcord - Gateway Error", MB_ICONERROR);
		}
		const char *error = cJSON_GetErrorPtr();
		if (error) {
			printf("Parse error before: %s\n", error);
			return;
		}
	}

	int opcode = (cJSON_GetObjectItem(json, "op"))->valueint;
	cJSON *data = (cJSON_GetObjectItem(json, "d"));

	if (opcode == 10) {
		// received hello.. discord api wants us to send a heartbeat rn and
		// identify

		SendWebSocket(ssl, "{\"op\": 1,\"d\": null}", 19, FIN_LAST | WS_TEXT);
		char identifybuf[650 + strlen(token)];
		wsprintf(
			identifybuf,
			"{\"op\":2,\"d\":{\"token\":\"%s\",\"properties\":{\"os\":"
			"\"Linux\",\"browser\":\"Chrome\",\"device\":\"\",\"has_client_"
			"mods\":false,\"browser_user_agent\":\"Mozilla/5.0 (Linux; Android "
			"6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) "
			"Chrome/145.0.0.0 Mobile Safari/537.36\",\"release_channel\": "
			"\"stable\"},\"presence\":{},\"compress\":false,"
			"\"client_state\":{\"guild_versions\":{}}}}",
			token);
		SendWebSocket(ssl, identifybuf, strlen(identifybuf), 0x81);;
		SetTimer(NULL, 0,
				 cJSON_GetObjectItem(data, "heartbeat_interval")->valueint * 4 /
					 5,
				 HeartbeatProc);

	} else if (opcode == 11) {
		// heartbeat ack

	} else if (opcode == 1) {
		// discords heart fluttered :wilted_rose:
		SendWebSocket(GatewaySSL, "{\"op\": 1,\"d\": null}", 19, 0x81);
	} else if (opcode == 0) {
		HandleDiscordDispatch(json);
	} else if (opcode == 7) {
		MessageBoxA(NULL, "reconnect", "discordapi.c", 0);
	}
	cJSON_Delete(json);
}
