#pragma once

#include "core/convar/convar.h"
#include "server/serverpresence.h"
#include <winsock2.h>
#include <string>
#include <cstring>
#include <future>
#include <unordered_set>

extern ConVar* Cvar_ns_masterserver_hostname;
extern ConVar* Cvar_ns_curl_log_enable;

struct RemoteModInfo
{
public:
	std::string Name;
	std::string Version;
};

class RemoteServerInfo
{
public:
	char id[33]; // 32 bytes + nullterminator

	// server info
	char name[64];
	std::string description;
	char map[32];
	char playlist[16];
	char region[32];
	std::vector<RemoteModInfo> requiredMods;

	int playerCount;
	int maxPlayers;

	// connection stuff
	bool requiresPassword;

public:
	RemoteServerInfo(
		const char* newId,
		const char* newName,
		const char* newDescription,
		const char* newMap,
		const char* newPlaylist,
		const char* newRegion,
		int newPlayerCount,
		int newMaxPlayers,
		bool newRequiresPassword);
};

struct RemoteServerConnectionInfo
{
public:
	char authToken[32];

	in_addr ip;
	unsigned short port;
};

struct MainMenuPromoData
{
public:
	std::string newInfoTitle1;
	std::string newInfoTitle2;
	std::string newInfoTitle3;

	std::string largeButtonTitle;
	std::string largeButtonText;
	std::string largeButtonUrl;
	int largeButtonImageIndex;

	std::string smallButton1Title;
	std::string smallButton1Url;
	int smallButton1ImageIndex;

	std::string smallButton2Title;
	std::string smallButton2Url;
	int smallButton2ImageIndex;
};

class MasterServerManager
{
private:
	bool m_bRequestingServerList = false;
	bool m_bAuthenticatingWithGameServer = false;

public:
	char m_sOwnServerId[33];
	char m_sOwnServerAuthToken[33];
	char m_sOwnClientAuthToken[33];

	std::string m_sOwnModInfoJson;

	bool m_bOriginAuthWithMasterServerDone = false;
	bool m_bOriginAuthWithMasterServerInProgress = false;

	bool m_bOriginAuthWithMasterServerSuccessful = false;
	std::string m_sOriginAuthWithMasterServerErrorCode = "";
	std::string m_sOriginAuthWithMasterServerErrorMessage = "";

	bool m_bSavingPersistentData = false;

	bool m_bScriptRequestingServerList = false;
	bool m_bSuccessfullyConnected = true;

	bool m_bNewgameAfterSelfAuth = false;
	bool m_bScriptAuthenticatingWithGameServer = false;
	bool m_bSuccessfullyAuthenticatedWithGameServer = false;
	std::string m_sAuthFailureReason {};

	bool m_bHasPendingConnectionInfo = false;
	RemoteServerConnectionInfo m_pendingConnectionInfo;

	std::vector<RemoteServerInfo> m_vRemoteServers;

	bool m_bHasMainMenuPromoData = false;
	MainMenuPromoData m_sMainMenuPromoData;

	std::optional<RemoteServerInfo> m_currentServer;
	std::string m_sCurrentServerPassword;

	std::unordered_set<std::string> m_handledServerConnections;

public:
	MasterServerManager();

	void ClearServerList();
	void RequestServerList();
	void RequestMainMenuPromos();
	void AuthenticateOriginWithMasterServer(const char* uid, const char* originToken);
	void AuthenticateWithOwnServer(const char* uid, const char* playerToken);
	void AuthenticateWithServer(const char* uid, const char* playerToken, RemoteServerInfo server, const char* password);
	void WritePlayerPersistentData(const char* playerId, const char* pdata, size_t pdataSize);
	void ProcessConnectionlessPacketSigreq1(std::string req);
};

extern MasterServerManager* g_pMasterServerManager;
extern ConVar* Cvar_ns_masterserver_hostname;

/** Result returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */
enum class MasterServerReportPresenceResult
{
	// Adding this server to the MS was successful.
	Success,
	// We failed to add this server to the MS and should retry.
	Failed,
	// We failed to add this server to the MS and shouldn't retry.
	FailedNoRetry,
	// We failed to even reach the MS.
	FailedNoConnect,
	// We failed to add the server because an existing server with the same ip:port exists.
	FailedDuplicateServer,
};

class MasterServerPresenceReporter : public ServerPresenceReporter
{
public:
	/** Full data returned in the std::future of a MasterServerPresenceReporter::ReportPresence() call. */
	struct ReportPresenceResultData
	{
		MasterServerReportPresenceResult result;

		std::optional<std::string> id;
		std::optional<std::string> serverAuthToken;
	};

	const int MAX_REGISTRATION_ATTEMPTS = 5;

	// Called to initialise the master server presence reporter's state.
	void CreatePresence(const ServerPresence* pServerPresence) override;

	// Run on an internal to either add the server to the MS or update it.
	void ReportPresence(const ServerPresence* pServerPresence) override;

	// Called when we need to remove the server from the master server.
	void DestroyPresence(const ServerPresence* pServerPresence) override;

	// Called every frame.
	void RunFrame(double flCurrentTime, const ServerPresence* pServerPresence) override;

protected:
	// Contains the async logic to add the server to the MS.
	void InternalAddServer(const ServerPresence* pServerPresence);

	// Contains the async logic to update the server on the MS.
	void InternalUpdateServer(const ServerPresence* pServerPresence);

	// The future used for InternalAddServer() calls.
	std::future<ReportPresenceResultData> addServerFuture;

	// The future used for InternalAddServer() calls.
	std::future<ReportPresenceResultData> updateServerFuture;

	int m_nNumRegistrationAttempts;

	double m_fNextAddServerAttemptTime;
};