#include "CMGameInstance.h"
#include "SocketIOClient.h"
#include "Kismet/GameplayStatics.h"

void UCMGameInstance::Login(const FString& Username, const FString& Password)
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/login");
	Request->SetVerb("POST");
	Request->SetHeader("Content-Type", "application/json");
	Request->SetHeader("Accept", "application/json");
	Request->SetContentAsString("{\"username\":\"" + Username + "\",\"password\":\"" + Password + "\"}");
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToLoginComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::Register(const FString& Username, const FString& Password)
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/register");
	Request->SetVerb("POST");
	Request->SetHeader("Content-Type", "application/json");
	Request->SetHeader("Accept", "application/json");
	Request->SetContentAsString("{\"username\":\"" + Username + "\",\"password\":\"" + Password + "\"}");
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToRegisterComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::Logout()
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/logout");
	Request->SetVerb("POST");
	Request->SetHeader("Authorization", "Bearer " + JwtToken);
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToLogoutComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::CreateLobby(const int MaxPlayers)
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/create");
	Request->SetVerb("POST");
	Request->SetHeader("Authorization", "Bearer " + JwtToken);
	Request->SetHeader("Content-Type", "application/json");
	Request->SetHeader("Accept", "application/json");
	Request->SetContentAsString("{\"maxPlayers\":" + FString::FromInt(MaxPlayers) + "}");
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToCreateLobbyComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::JoinLobby(const FString LobbyID)
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/join");
	Request->SetVerb("POST");
	Request->SetHeader("Authorization", "Bearer " + JwtToken);
	Request->SetHeader("Content-Type", "application/json");
	Request->SetHeader("Accept", "application/json");
	const TSharedPtr<FJsonObject> JoinData = MakeShareable(new FJsonObject);
	JoinData->SetStringField("lobbyID", LobbyID);
	JoinData->SetStringField("username", AccountUsername);
	Request->SetContentAsString(USIOJConvert::ToJsonString(JoinData));
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToJoinLobbyComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::LeaveLobby()
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/leave");
	Request->SetVerb("POST");
	Request->SetHeader("Authorization", "Bearer " + JwtToken);
	Request->SetHeader("Content-Type", "application/json");
	Request->SetHeader("Accept", "application/json");
	const TSharedPtr<FJsonObject> JoinData = MakeShareable(new FJsonObject);
	JoinData->SetStringField("lobbyID", JoinedLobbyID);
	JoinData->SetStringField("username", AccountUsername);
	Request->SetContentAsString(USIOJConvert::ToJsonString(JoinData));
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToLeaveLobbyComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::GetPlayerLobbyList()
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/lobby/players/" + JoinedLobbyID);
	Request->SetVerb("GET");
	Request->SetHeader("Authorization", "Bearer " + JwtToken);
	Request->SetHeader("Accept", "application/json");
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToGetLobbyPlayersComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::GetServerLobbyList()
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/lobbies");
	Request->SetVerb("GET");
	Request->SetHeader("Authorization", "Bearer " + JwtToken);
	Request->SetHeader("Accept", "application/json");
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToGetServerLobbyListComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::GetLobbyByID(const FString LobbyID)
{
	const auto Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(LobbyAPI + "/lobbies/id/" + LobbyID);
	Request->SetVerb("GET");
	Request->SetHeader("Authorization", "Bearer " + JwtToken);
	Request->SetHeader("Accept", "application/json");
	Request->OnProcessRequestComplete().BindUObject(this, &UCMGameInstance::OnRequestToGetLobbyByIDComplete);
	Request->ProcessRequest();
}

void UCMGameInstance::ReadyUp()
{
	const TSharedPtr<FJsonObject> ReadyUp = MakeShareable(new FJsonObject);
	ReadyUp->SetStringField("lobbyID", JoinedLobbyID);
	ReadyUp->SetStringField("username", AccountUsername);
	Socket->Emit(TEXT("ready"), ReadyUp);
}

void UCMGameInstance::UnReady()
{
	const TSharedPtr<FJsonObject> UnReady = MakeShareable(new FJsonObject);
	UnReady->SetStringField("lobbyID", JoinedLobbyID);
	UnReady->SetStringField("username", AccountUsername);
	Socket->Emit(TEXT("unready"), UnReady);
}

void UCMGameInstance::StartGame()
{
	Socket->Emit(TEXT("start"), JoinedLobbyID);
	if (APlayerController* PC = GetWorld()->GetFirstPlayerController())
	{
		PC->ConsoleCommand("open " + ServerFQDN + ":" + FString::FromInt(ServerPort));
	}
}

void UCMGameInstance::Init()
{
	Super::Init();

	Socket = ISocketIOClientModule::Get().NewValidNativePointer();

	Socket->Connect("wss://cm.api.philipwhite.dev/");
	Socket->OnConnectedCallback = [this](const FString& SocketID, const FString& SessionID)
	{
		UE_LOG(LogTemp, Log, TEXT("Connected: %s %s"), *SocketID, *SessionID);
		if (IsDedicatedServerInstance())
		{
			FParse::Value(FCommandLine::Get(), TEXT("lobbyID="), JoinedLobbyID);
			FString CMServerSecret;
			FParse::Value(FCommandLine::Get(), TEXT("CMServerSecret="), CMServerSecret);
			const TSharedPtr<FJsonObject> AuthorityData = MakeShareable(new FJsonObject);
			AuthorityData->SetStringField("lobbyID", JoinedLobbyID);
			AuthorityData->SetStringField("secret", CMServerSecret);
			Socket->Emit(TEXT("authority"), AuthorityData);
		}
		else
		{
			const TSharedPtr<FJsonObject> AuthorityData = MakeShareable(new FJsonObject);
			AuthorityData->SetStringField("authority", "client");
			Socket->Emit(TEXT("authority"), AuthorityData);
		}
	};

	Socket->OnEvent("command", [this](const FString& Event, const TSharedPtr<FJsonValue>& Command)
	{
		if (IsDedicatedServerInstance())
		{
			if (Command->AsString() == "stop")
			{
				UE_LOG(LogTemp, Log, TEXT("Shutting Down Server"));
				FGenericPlatformMisc::RequestExit(false);
			}
		}
	});

	Socket->OnEvent("join", [this](const FString& Event, const TSharedPtr<FJsonValue>& Message)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(USIOJConvert::ToJsonString(Message));
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonUsername = JsonObject->GetStringField("username");
			OnPlayerJoin.Broadcast(JsonUsername);
		}
	});

	Socket->OnEvent("leave", [this](const FString& Event, const TSharedPtr<FJsonValue>& Message)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(USIOJConvert::ToJsonString(Message));
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonUsername = JsonObject->GetStringField("username");
			OnPlayerLeave.Broadcast(JsonUsername);
		}
	});

	Socket->OnEvent("close", [this](const FString& Event, const TSharedPtr<FJsonValue>& Message)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(USIOJConvert::ToJsonString(Message));
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonMessage = JsonObject->GetStringField("message");
			if (GetWorld()->GetName() != "ClientEntry")
			{
				UGameplayStatics::OpenLevel(GetWorld(), "ClientEntry");
			}
			OnServerClose.Broadcast(JsonMessage);
		}
	});

	Socket->OnEvent("ready", [this](const FString& Event, const TSharedPtr<FJsonValue>& Message)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(USIOJConvert::ToJsonString(Message));
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonUsername = JsonObject->GetStringField("username");
			OnPlayerReady.Broadcast(JsonUsername);
		}
	});

	Socket->OnEvent("unready", [this](const FString& Event, const TSharedPtr<FJsonValue>& Message)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(USIOJConvert::ToJsonString(Message));
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonUsername = JsonObject->GetStringField("username");
			OnPlayerUnReady.Broadcast(JsonUsername);
		}
	});

	Socket->OnEvent("start", [this](const FString& Event, const TSharedPtr<FJsonValue>& Message)
	{
		if (GetWorld()->GetName() != "ClientEntry") return;
		if (APlayerController* PC = GetWorld()->GetFirstPlayerController())
		{
			PC->ConsoleCommand("open " + ServerFQDN + ":" + FString::FromInt(ServerPort));
		}
	});
}

void UCMGameInstance::Shutdown()
{
	Super::Shutdown();
	if (Socket.IsValid())
	{
		ISocketIOClientModule::Get().ReleaseNativePointer(Socket);
		Socket = nullptr;
	}
}

void UCMGameInstance::OnRequestToLoginComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonToken = JsonObject->GetStringField("token");
			const auto JsonUsername = JsonObject->GetStringField("user");
			JwtToken = JsonToken;
			AccountUsername = JsonUsername;
			Socket->Emit(TEXT("login"), JsonUsername);
			OnLoginComplete.Broadcast(true);
		}
		else
		{
			OnLoginComplete.Broadcast(false);
		}
	}
	else
	{
		OnLoginComplete.Broadcast(false);
	}
}

void UCMGameInstance::OnRequestToRegisterComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) const
{
	if (Response->GetResponseCode() == 201)
	{
		OnRegisterComplete.Broadcast(true);
	}
	else
	{
		OnRegisterComplete.Broadcast(false);
	}
}

void UCMGameInstance::OnRequestToLogoutComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		AccountUsername = "";
		JwtToken = "";
		OnLogoutComplete.Broadcast(true);
	}
	else
	{
		OnLogoutComplete.Broadcast(false);
	}
}

void UCMGameInstance::OnRequestToCreateLobbyComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonLobbyID = JsonObject->GetStringField("lobbyID");
			const auto JsonPort = JsonObject->GetIntegerField("port");
			OnCreateLobbyComplete.Broadcast(true);
			JoinLobby(JsonLobbyID);
		}
		else
		{
			OnCreateLobbyComplete.Broadcast(false);
		}
	}
	else
	{
		OnCreateLobbyComplete.Broadcast(false);
	}
}

void UCMGameInstance::OnRequestToJoinLobbyComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonLobbyID = JsonObject->GetStringField("lobbyID");
			const auto JsonPort = JsonObject->GetIntegerField("port");
			JoinedLobbyID = JsonLobbyID;
			ServerPort = JsonPort;
			const TSharedPtr<FJsonObject> JoinData = MakeShareable(new FJsonObject);
			JoinData->SetStringField("lobbyID", JoinedLobbyID);
			JoinData->SetStringField("username", AccountUsername);
			Socket->Emit(TEXT("join"), JoinData);
			OnJoinLobbyComplete.Broadcast(true);
		}
		else
		{
			OnJoinLobbyComplete.Broadcast(false);
		}
	}
	else
	{
		OnJoinLobbyComplete.Broadcast(false);
	}
}

void UCMGameInstance::OnRequestToLeaveLobbyComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		const TSharedPtr<FJsonObject> LeaveData = MakeShareable(new FJsonObject);
		LeaveData->SetStringField("lobbyID", JoinedLobbyID);
		LeaveData->SetStringField("username", AccountUsername);
		Socket->Emit(TEXT("leave"), LeaveData);
		OnLeaveLobbyComplete.Broadcast(true);
	}
	else
	{
		OnLeaveLobbyComplete.Broadcast(false);
	}
}

void UCMGameInstance::OnRequestToGetLobbyPlayersComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (TArray<TSharedPtr<FJsonValue>> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			PlayersInLobby.Empty();
			for (const auto& Player : JsonObject)
			{
				const auto JsonUsername = Player->AsObject()->GetStringField("user");
				const auto JsonIsOwner = Player->AsObject()->GetBoolField("isOwner");
				const auto JsonIsReady = Player->AsObject()->GetBoolField("isReady");
				PlayersInLobby.Add(FPlayerInLobby(JsonUsername, JsonIsOwner, JsonIsReady));
			}
			PlayerIndex = PlayersInLobby.IndexOfByPredicate([this](const FPlayerInLobby& Player)
			{
				return Player.Username == AccountUsername;
			});
			OnGetPlayersInLobbyComplete.Broadcast(true);
		}
		else
		{
			OnGetPlayersInLobbyComplete.Broadcast(false);
		}
	}
	else
	{
		OnGetPlayersInLobbyComplete.Broadcast(false);
	}
}

void UCMGameInstance::OnRequestToGetServerLobbyListComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (TArray<TSharedPtr<FJsonValue>> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			ServerLobbies.Empty();
			for (const auto& Server : JsonObject)
			{
				const auto JsonLobbyID = Server->AsObject()->GetStringField("id");
				const auto JsonStatus = Server->AsObject()->GetStringField("status");
				const auto JsonOnlinePlayers = Server->AsObject()->GetIntegerField("online");
				const auto JsonMaxPlayers = Server->AsObject()->GetIntegerField("maxPlayers");
				ServerLobbies.Add(FServerLobby(JsonLobbyID, JsonStatus, JsonOnlinePlayers, JsonMaxPlayers));
			}
			OnServerLobbyListComplete.Broadcast(true);
		}
		else
		{
			OnServerLobbyListComplete.Broadcast(false);
		}
	}
	else
	{
		OnServerLobbyListComplete.Broadcast(false);
	}
}

void UCMGameInstance::OnRequestToGetLobbyByIDComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		const auto JsonReader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
		if (TSharedPtr<FJsonObject> JsonObject; FJsonSerializer::Deserialize(JsonReader, JsonObject))
		{
			const auto JsonLobbyID = JsonObject->GetStringField("id");
			const auto JsonMaxPlayers = JsonObject->GetIntegerField("maxPlayers");
			OnGetLobbyByID.Broadcast(true, JsonLobbyID, JsonMaxPlayers);
		}
		else
		{
			OnGetLobbyByID.Broadcast(false, "AAAAA", 0);
		}
	}
	else
	{
		OnGetLobbyByID.Broadcast(false, "AAAAA", 0);
	}
}
