#pragma semicolon 1
#include <sourcemod>
#include <SteamWorks>

#pragma newdecls required
#include <GFLClanru>

//#define GFL_API_KEY "secret"
#include "GFLClanruAPI.secret"

bool g_bLateLoad = false;
float g_fMonthlyCosts = 45.0;
KeyValues g_Response[MAXPLAYERS + 1];
bool g_bResponseFailed[MAXPLAYERS + 1];
bool g_bClientPreAdminChecked[MAXPLAYERS + 1];

public Plugin myinfo =
{
	name = "GFLCLan.ru API Integration",
	author = "BotoX",
	description = "Handles donators.",
	version = "0.1",
	url = ""
}

public void OnPluginStart()
{
	RegConsoleCmd("sm_tier", Command_Tier, "[GFLClan.ru] Displays donator info.");
	RegConsoleCmd("sm_vip", Command_Tier, "[GFLClan.ru] Displays donator info.");
}

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
	CreateNative("AsyncHasSteamIDReservedSlot", Native_AsyncHasSteamIDReservedSlot);
	RegPluginLibrary("GFLClanru");

	g_bLateLoad = late;
	return APLRes_Success;
}

public void OnRebuildAdminCache(AdminCachePart part)
{
	if(part != AdminCache_Admins)
		return;

	CreateTimer(1.0, OnRebuildAdminCachePost, 0, TIMER_FLAG_NO_MAPCHANGE);
}

public Action OnRebuildAdminCachePost(Handle timer)
{
	for(int client = 1; client <= MaxClients; client++)
	{
		if(g_bClientPreAdminChecked[client] && g_Response[client])
			OnReceiveUser(client);
	}

	return Plugin_Stop;
}

public void OnClientConnected(int client)
{
	g_bClientPreAdminChecked[client] = false;
	g_bResponseFailed[client] = false;
}

public void OnClientAuthorized(int client, const char[] auth)
{
	if(IsFakeClient(client))
		return;

	char sSteam64ID[32];
	Steam32IDtoSteam64ID(auth, sSteam64ID, sizeof(sSteam64ID));

	int UserSerial = GetClientSerial(client);

	static char sRequest[256];
	FormatEx(sRequest, sizeof(sRequest), "http://direct.gflclan.ru/api/self/Server/OnClientAuthorized?key=%s&steamid=%s", GFL_API_KEY, sSteam64ID);

	Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest);
	if (!hRequest ||
		!SteamWorks_SetHTTPRequestContextValue(hRequest, UserSerial) ||
		!SteamWorks_SetHTTPCallbacks(hRequest, OnClientAuthorized_OnTransferComplete) ||
		!SteamWorks_SetHTTPRequestHeaderValue(hRequest, "Accept", "application/vdf") ||
		!SteamWorks_SendHTTPRequest(hRequest))
	{
		LogError("%L SteamWorks_CreateHTTPRequest failed.", client);
		CloseHandle(hRequest);
		g_bResponseFailed[client] = true;
	}

	return;
}

public int OnClientAuthorized_OnTransferComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, int UserSerial)
{
	int client = GetClientFromSerial(UserSerial);
	if(!client) // Player disconnected
	{
		CloseHandle(hRequest);
		return;
	}

	if(bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK)
	{
		LogError("%L OnClientAuthorized HTTP Response failed: %d", client, eStatusCode);
		CloseHandle(hRequest);
		g_bResponseFailed[client] = true;

		if(g_bClientPreAdminChecked[client])
			NotifyPostAdminCheck(client);

		return;
	}

	SteamWorks_GetHTTPResponseBodyCallback(hRequest, OnClientAuthorized_APIWebResponse, UserSerial);
	CloseHandle(hRequest);
}

public int OnClientAuthorized_APIWebResponse(const char[] sData, int UserSerial)
{
	int client = GetClientFromSerial(UserSerial);
	if(!client) // Player disconnected
		return;

	KeyValues Response = new KeyValues("OnClientAuthorized_APIWebResponse");
	if(!Response.ImportFromString(sData, "OnClientAuthorized_APIWebResponse"))
	{
		LogError("%L ImportFromString(sData, \"OnClientAuthorized_APIWebResponse\") failed.", client);
		delete Response;

		if(g_bClientPreAdminChecked[client])
			NotifyPostAdminCheck(client);

		return;
	}
	g_Response[client] = Response;

	if(g_bClientPreAdminChecked[client])
	{
		LogMessage("%L APIWebResponse late.", client);
		NotifyPostAdminCheck(client);
	}
}

public Action OnClientPreAdminCheck(int client)
{
	g_bClientPreAdminChecked[client] = true;
	if(g_Response[client] || g_bResponseFailed[client])
		return Plugin_Continue;

	RunAdminCacheChecks(client);

	return Plugin_Handled;
}

public void OnClientPostAdminFilter(int client)
{
	OnReceiveUser(client);
}

public void OnClientDisconnect(int client)
{
	g_bClientPreAdminChecked[client] = false;
	g_bResponseFailed[client] = false;
	delete g_Response[client];
}

void OnReceiveUser(int client)
{
	KeyValues Response = g_Response[client];
	if(!Response)
		return;

	ArrayList Groups = new ArrayList(ByteCountToCells(32));

	if(Response.JumpToKey("forum"))
	{
		int Member = Response.GetNum("member");
		if(Member)
		{
			int LastSeen = Response.GetNum("last_seen");
			int Expires = RoundFloat(LastSeen + 86400.0 * 7.0);
			int Now = GetTime();

			if(Now < Expires)
			{
				Groups.PushString("Member");
			}
		}
	}
	Response.Rewind();

	if(Response.JumpToKey("donations") && Response.GotoFirstSubKey())
	{
		do
		{
			char sGroup[32];
			Response.GetString("group", sGroup, sizeof(sGroup));
			char sState[32];
			Response.GetString("state", sState, sizeof(sState));
			int Tier = Response.GetNum("tier");
			int Deactivate = Response.GetNum("deactivate", 0);

			if(StrEqual(sState, "active"))
			{
				Groups.PushString(sGroup);
			}
			else if(Deactivate && (StrEqual(sState, "expired") || StrEqual(sState, "refunded") || StrEqual(sState, "reversed")))
			{
				0;
			}
		}
		while(Response.GotoNextKey());
	}
	Response.Rewind();

	if(!Groups.Length)
	{
		delete Groups;
		return;
	}

	AdminId adm;
	// Use a pre-existing admin if we can
	if((adm = GetUserAdmin(client)) == INVALID_ADMIN_ID)
	{
		LogMessage("Creating new admin for %L", client);
		adm = CreateAdmin("");
		SetUserAdmin(client, adm, true);
	}

	for(int i = 0; i < Groups.Length; i++)
	{
		char sGroup[32];
		Groups.GetString(i, sGroup, sizeof(sGroup));

		GroupId grp;
		if((grp = FindAdmGroup(sGroup)) != INVALID_GROUP_ID)
		{
			LogMessage("Adding %L to group %s", client, sGroup);
			AdminInheritGroup(adm, grp);
		}
		else
			LogError("%L Group %s not found!", client, sGroup);
	}

	delete Groups;
}

public void OnClientPostAdminCheck(int client)
{
	KeyValues Response = g_Response[client];
	if(!Response)
		return;

	Response.JumpToKey("donations");
	Response.GotoFirstSubKey();
	do
	{
		int Created = Response.GetNum("created");
		int Activated = Response.GetNum("activated");
		int Expires = Response.GetNum("expires");
		int Length = Response.GetNum("length");
		char sGroup[32];
		Response.GetString("group", sGroup, sizeof(sGroup));
		char sState[32];
		Response.GetString("state", sState, sizeof(sState));
		int Tier = Response.GetNum("tier");
		int Anonymous = Response.GetNum("anonymous");
		int New = Response.GetNum("new");
		int Deactivate = Response.GetNum("deactivate", 0);
		float fNetAmount = Response.GetFloat("net_amount", 0.0);

		if(StrEqual(sState, "active"))
		{
			static char sExpireDate[32];
			FormatTime(sExpireDate, sizeof(sExpireDate), "%a, %d %b %Y %H:%M:%S +00", Expires);

			int Remaining = Expires - Created;
			float RemainingDays = Remaining / 3600.0 / 24.0;

			PrintToChat(client, "\x04[GFLClan.ru]\x01 Donator \x03Tier %d\x01 enabled. Valid until %s (%.1f days)",
				Tier, sExpireDate, RemainingDays);

			if(New)
			{
				float fDays = fNetAmount * (30.5 / g_fMonthlyCosts);
				PrintCenterText(client, "Your Tier %d donation has been activated! Thank you <3", Tier);
				if(fNetAmount && !Anonymous)
					PrintToChatAll("\x04[GFLClan.ru]\x01 \x03%N\x01's donation paid for %.1f days of server uptime, thanks!", client, fDays);
			}
			else if(Remaining < 86400) // less than 24 hours
			{
				int Hours = RoundToFloor(Remaining / 3600.0);
				int Minutes = Remaining % 60;
				PrintCenterText(client, "Oy vey goyim! Your tier %d donation will expire in %d hours and %d minutes.", Tier, Hours, Minutes);
			}
		}
		else if(StrEqual(sState, "queued"))
		{
			float Days = Length / 3600.0 / 24.0;
			PrintToChat(client, "\x04[GFLClan.ru]\x01 Donator \x03Tier %d\x01 queued. Length: %.1f days",
				Tier, Days);
		}
		else if(StrEqual(sState, "expired"))
		{
			PrintCenterText(client, "Oy gevalt goyim! Your tier %d donation has expired.", Tier);
		}
		else if(StrEqual(sState, "refunded"))
		{
			PrintCenterText(client, "OY GEVALT GOYIM YOUR DONATION HAS BEEN REFUNDED!");
		}
		else if(StrEqual(sState, "reversed"))
		{
			PrintCenterText(client, "OY GEVALT GOYIM YOUR DONATION HAS BEEN REVERSED!");
		}
	}
	while(Response.GotoNextKey());
	Response.Rewind();

	Response.JumpToKey("forum");
	Response.GotoFirstSubKey();
	int Member = Response.GetNum("member");
	if(Member)
	{
		int LastSeen = Response.GetNum("last_seen");
		int Expires = RoundFloat(LastSeen + 86400.0 * 7.0);
		int Now = GetTime();

		if(Now < Expires)
		{
			static char sExpireDate[32];
			FormatTime(sExpireDate, sizeof(sExpireDate), "%a, %d %b %Y %H:%M:%S +00", Expires);

			PrintToChat(client, "\x04[GFLClan.ru]\x01 \x03Member\x01 enabled. Remember to log in until %s (%.1f days)",
				sExpireDate, (Expires - Now) / 3600.0 / 24.0);
		}
	}
}

public Action Command_Tier(int client, int args)
{
	KeyValues Response = g_Response[client];
	if(!Response)
	{
		ReplyToCommand(client, "\x04[GFLClan.ru]\x01 No donator info available!");
		return Plugin_Handled;
	}

	Response.JumpToKey("donations");
	Response.GotoFirstSubKey();
	do
	{
		int Created = Response.GetNum("created");
		int Expires = Response.GetNum("expires");
		int Length = Response.GetNum("length");
		char sState[32];
		Response.GetString("state", sState, sizeof(sState));
		int Tier = Response.GetNum("tier");

		if(StrEqual(sState, "active"))
		{
			static char sExpireDate[32];
			FormatTime(sExpireDate, sizeof(sExpireDate), "%a, %d %b %Y %H:%M:%S +00", Expires);

			int Remaining = Expires - Created;
			float RemainingDays = Remaining / 3600.0 / 24.0;

			PrintToChat(client, "\x04[GFLClan.ru]\x01 Donator \x03Tier %d\x01 active. Valid until %s (%.1f days)",
				Tier, sExpireDate, RemainingDays);
		}
		else if(StrEqual(sState, "queued"))
		{
			float Days = Length / 3600.0 / 24.0;
			PrintToChat(client, "\x04[GFLClan.ru]\x01 Donator \x03Tier %d\x01 queued. Length: %.1f days",
				Tier, Days);
		}
	}
	while(Response.GotoNextKey());
	Response.Rewind();

	Response.JumpToKey("forum");
	Response.GotoFirstSubKey();
	int Member = Response.GetNum("member");
	if(Member)
	{
		int LastSeen = Response.GetNum("last_seen");
		int Expires = RoundFloat(LastSeen + 86400.0 * 7.0);
		int Now = GetTime();

		if(Now < Expires)
		{
			static char sExpireDate[32];
			FormatTime(sExpireDate, sizeof(sExpireDate), "%a, %d %b %Y %H:%M:%S +00", Expires);

			PrintToChat(client, "\x04[GFLClan.ru]\x01 \x03Member\x01 active. Remember to log in until %s (%.1f days)",
				sExpireDate, (Expires - Now) / 3600.0 / 24.0);
		}
	}

	return Plugin_Handled;
}

public int Native_AsyncHasSteamIDReservedSlot(Handle plugin, int numParams)
{
	char sSteam32ID[32];
	GetNativeString(1, sSteam32ID, sizeof(sSteam32ID));

	AsyncHasSteamIDReservedSlotCallbackFunc Callback;
	Callback = GetNativeCell(2);

	any Data;
	Data = GetNativeCell(3);

	char sSteam64ID[32];
	Steam32IDtoSteam64ID(sSteam32ID, sSteam64ID, sizeof(sSteam64ID));

	static char sRequest[256];
	FormatEx(sRequest, sizeof(sRequest), "http://direct.gflclan.ru/api/self/Server/HasSteamIDReservedSlot?key=%s&steamid=%s", GFL_API_KEY, sSteam64ID);

	DataPack Pack = new DataPack();
	Pack.WriteString(sSteam32ID);
	Pack.WriteCell(plugin);
	Pack.WriteFunction(Callback);
	Pack.WriteCell(Data);

	Handle hRequest = SteamWorks_CreateHTTPRequest(k_EHTTPMethodGET, sRequest);
	if (!hRequest ||
		!SteamWorks_SetHTTPRequestNetworkActivityTimeout(hRequest, 3) ||
		!SteamWorks_SetHTTPRequestContextValue(hRequest, Pack) ||
		!SteamWorks_SetHTTPCallbacks(hRequest, Native_AsyncHasSteamIDReservedSlot_OnTransferComplete) ||
		!SteamWorks_SendHTTPRequest(hRequest))
	{
		CloseHandle(hRequest);
	}
}

public int Native_AsyncHasSteamIDReservedSlot_OnTransferComplete(Handle hRequest, bool bFailure, bool bRequestSuccessful, EHTTPStatusCode eStatusCode, DataPack Pack)
{
	if(bFailure || !bRequestSuccessful || eStatusCode != k_EHTTPStatusCode200OK)
	{
		LogError("Native_AsyncHasSteamIDReservedSlot HTTP Response failed: %d", eStatusCode);
		CloseHandle(hRequest);
		// Simulate false response
		char sData[2] = "0";
		Native_AsyncHasSteamIDReservedSlot_APIWebResponse(sData, Pack);
		return;
	}

	SteamWorks_GetHTTPResponseBodyCallback(hRequest, Native_AsyncHasSteamIDReservedSlot_APIWebResponse, Pack);
	CloseHandle(hRequest);
}

public int Native_AsyncHasSteamIDReservedSlot_APIWebResponse(char[] sData, DataPack Pack)
{
	Pack.Reset();
	char sSteam32ID[32];
	Pack.ReadString(sSteam32ID, sizeof(sSteam32ID));

	Handle plugin;
	plugin = Pack.ReadCell();

	AsyncHasSteamIDReservedSlotCallbackFunc Callback;
	Callback = view_as<AsyncHasSteamIDReservedSlotCallbackFunc>(Pack.ReadFunction());

	any Data;
	Data = Pack.ReadCell();

	delete Pack;

	TrimString(sData);
	int Result = StringToInt(sData);

	Call_StartFunction(plugin, Callback);
	Call_PushString(sSteam32ID);
	Call_PushCell(Result);
	Call_PushCell(Data);
	Call_Finish();

	return 0;
}


stock bool Steam32IDtoSteam64ID(const char[] sSteam32ID, char[] sSteam64ID, int Size)
{
	if(strlen(sSteam32ID) < 11 || strncmp(sSteam32ID[0], "STEAM_", 6))
	{
		sSteam64ID[0] = 0;
		return false;
	}

	int iUpper = 765611979;
	int isSteam64ID = StringToInt(sSteam32ID[10]) * 2 + 60265728 + sSteam32ID[8] - 48;

	int iDiv = isSteam64ID / 100000000;
	int iIdx = 9 - (iDiv ? (iDiv / 10 + 1) : 0);
	iUpper += iDiv;

	IntToString(isSteam64ID, sSteam64ID[iIdx], Size - iIdx);
	iIdx = sSteam64ID[9];
	IntToString(iUpper, sSteam64ID, Size);
	sSteam64ID[9] = iIdx;

	return true;
}