#include "SampleGameGameModeBase.h"

#include "MyPlayerController.h"
#include "PragmaSdkConfig.h"
#include "PragmaServer.h"
#include "AgonesSubsystem.h"
#include "Misc/FileHelper.h"

void APRGMSampleGameGameModeBase::BeginPlay()
{
	Super::BeginPlay();

	UE_LOG(LogTemp, Display, TEXT("Initializing Pragma SDK..."));

	ServerSubsystem = GetGameInstance()->GetSubsystem<UPragmaGameServerSubsystem>();

	if (IsRunningDedicatedServer())
	{
		UE_LOG(LogTemp, Warning, TEXT("IsRunningDedicatedServer is true. We are server."));
		PragmaServer = ServerSubsystem->Server();
		InitCommandLineParameters();

		if (!PragmaGameInstanceId.IsEmpty())
		{
			// Local development: all params provided via command line, connect immediately.
			UE_LOG(LogTemp, Warning, TEXT("Local mode: PragmaGameInstanceId on command line, connecting to Pragma immediately."));
			ConnectToPragma();
		}
		else
		{
			// GameFabric mode: Agones Ready/Health are handled automatically by UAgonesSubsystem.
			// Poll for the allocation payload file written by the allocation sidecar.
			UE_LOG(LogTemp, Warning, TEXT("GameFabric mode: waiting for allocation payload at %s"), PayloadFilePath);
			GetWorldTimerManager().SetTimer(
				PayloadPollTimerHandle,
				this,
				&APRGMSampleGameGameModeBase::CheckForPayloadFile,
				2.0f,
				true
			);
		}
	}
}

void APRGMSampleGameGameModeBase::CheckForPayloadFile()
{
	if (bAllocated)
	{
		return;
	}

	if (FPaths::FileExists(PayloadFilePath))
	{
		UE_LOG(LogTemp, Warning, TEXT("GameFabric: Payload file found at %s"), PayloadFilePath);
		GetWorldTimerManager().ClearTimer(PayloadPollTimerHandle);

		if (ReadPayloadFile(PayloadFilePath))
		{
			bAllocated = true;
			OnAllocationPayloadReceived();
		}
		else
		{
			UE_LOG(LogTemp, Error, TEXT("GameFabric: Failed to parse payload file. Shutting down."));
			ShutdownServer();
		}
	}
}

bool APRGMSampleGameGameModeBase::ReadPayloadFile(const FString& FilePath)
{
	FString JsonString;
	if (!FFileHelper::LoadFileToString(JsonString, *FilePath))
	{
		UE_LOG(LogTemp, Error, TEXT("GameFabric: Could not read file %s"), *FilePath);
		return false;
	}

	TSharedPtr<FJsonObject> JsonObject;
	TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
	if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
	{
		UE_LOG(LogTemp, Error, TEXT("GameFabric: Failed to deserialize JSON from %s"), *FilePath);
		return false;
	}

	if (JsonObject->HasField(TEXT("GameInstanceId")))
	{
		PragmaGameInstanceId = JsonObject->GetStringField(TEXT("GameInstanceId"));
	}
	if (JsonObject->HasField(TEXT("PartnerGameToken")))
	{
		PragmaPartnerSessionGameAuthToken = JsonObject->GetStringField(TEXT("PartnerGameToken"));
	}
	if (JsonObject->HasField(TEXT("PartnerSocialToken")))
	{
		PragmaPartnerSessionSocialAuthToken = JsonObject->GetStringField(TEXT("PartnerSocialToken"));
	}
	if (JsonObject->HasField(TEXT("pragmaBackendAddress")))
	{
		PragmaPartnerBackendAddress = JsonObject->GetStringField(TEXT("pragmaBackendAddress"));
	}

	PRAGMA_LOG(Warning, "GameFabric Payload | PragmaGameInstanceId: %s", *PragmaGameInstanceId);
	PRAGMA_LOG(Warning, "GameFabric Payload | PragmaBackendAddress: %s", *PragmaPartnerBackendAddress);
	PRAGMA_LOG(Warning, "GameFabric Payload | GameServerHostname: %s", *GameServerHostname);
	PRAGMA_LOG(Warning, "GameFabric Payload | GameServerPort: %d", static_cast<int32>(GameServerPort));

	return true;
}

void APRGMSampleGameGameModeBase::OnAllocationPayloadReceived()
{
	UE_LOG(LogTemp, Warning, TEXT("GameFabric: Allocation received, connecting to Pragma..."));
	ConnectToPragma();
}

void APRGMSampleGameGameModeBase::ConnectToPragma()
{
	ServerSubsystem->Runtime()->Config().PartnerBackendAddress = PragmaPartnerBackendAddress;

	// In GameFabric mode, auth tokens come from the allocation payload file.
	// In local mode, the SDK reads -PragmaPartnerSessionGameAuthToken / -PragmaPartnerSessionSocialAuthToken
	// from the command line automatically.
	if (!PragmaPartnerSessionGameAuthToken.IsEmpty())
	{
		ServerSubsystem->Runtime()->Config().PartnerSessionGameAuthToken = PragmaPartnerSessionGameAuthToken;
	}
	if (!PragmaPartnerSessionSocialAuthToken.IsEmpty())
	{
		ServerSubsystem->Runtime()->Config().PartnerSessionSocialAuthToken = PragmaPartnerSessionSocialAuthToken;
	}

	PragmaServer->Connect(Pragma::FServer::FConnectedDelegate::CreateUObject(this, &APRGMSampleGameGameModeBase::OnGameServerConnectedToPragma));
}


bool APRGMSampleGameGameModeBase::InitCommandLineParameters()
{
	PRAGMA_LOG(Warning, "Reading command line parameters");
	const TCHAR* Stream = FCommandLine::Get();

	FParse::Value(Stream, TEXT("-ExternalPort="), GameServerPort);
	FParse::Value(Stream, TEXT("-Hostname="), GameServerHostname);

	// These may be provided on the command line for local development.
	// In GameFabric mode they arrive via the allocation payload file instead.
	FParse::Value(Stream, TEXT("-PragmaPartnerBackendAddress="), PragmaPartnerBackendAddress);
	FParse::Value(Stream, TEXT("-PragmaGameInstanceId="), PragmaGameInstanceId);

	PRAGMA_LOG(Warning, "Command Line | GameServerHostname: %s", *GameServerHostname);
	PRAGMA_LOG(Warning, "Command Line | GameServerPort: %d", static_cast<int32>(GameServerPort));
	PRAGMA_LOG(Warning, "Command Line | PragmaBackendAddress: %s", *PragmaPartnerBackendAddress);
	PRAGMA_LOG(Warning, "Command Line | PragmaGameInstanceId: %s", *PragmaGameInstanceId);
	return true;
}

void APRGMSampleGameGameModeBase::EndGameMethod()
{
	UE_LOG(LogTemp, Display, TEXT(">>>>>>>>> Running EndGameMethod") );

	UPragmaMatchApi::FOnCompleteDelegate EndGameDelegate = UPragmaMatchApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("EndGame is Successful. Will ShutDown Server"));
		} else
		{
			PRAGMA_LOG(Error, "PragmaGameServer -- OnGameEnded Error: %s", *Result.Error().ToString());
		}
		ShutdownServer();
	});
	PragmaServer->MatchApi().EndGame(
		PragmaGameInstanceId,
		TArray<FPragma_GameInstance_PlayerGameResult>{},
		FPragma_GameInstance_ExtEndGameRequest{},
		EndGameDelegate);
}

void APRGMSampleGameGameModeBase::GrantCurrency(FString PlayerId)
{
	PRAGMA_LOG(Log, ">>>> Running GrantExperience on Game Server");
}

void APRGMSampleGameGameModeBase::OnGameServerConnectedToPragma(const TPragmaResult<>& Result)
{
	if (Result.IsFailure())
	{
		PRAGMA_LOG(Log, "Connect failed");
		ShutdownServer();
	} else
	{
		PRAGMA_LOG(Log, "GameServer Connected to Pragma");
		PragmaServer->MatchApi().OnPlayersAdded.AddUObject(this, &APRGMSampleGameGameModeBase::OnPlayersAddedTriggered);

		UPragmaMatchApi::FGameStartDataDelegate LinkDelegate = UPragmaMatchApi::FGameStartDataDelegate::CreateLambda([&](const TPragmaResult<FPragma_GameInstance_GameStart>& GameStartData)
		{
			if (GameStartData.IsSuccessful())
			{
				UE_LOG(LogTemp, Display, TEXT("Link successfully with gameInstanceId: %s"), *GameStartData->GameInstanceId);
				PRAGMA_LOG(Log, "Now I need to call ConnectPlayers for them. PlayerIds: %s", *GameStartData->Players[0].PlayerId);
				TArray<FPragma_GameInstance_PlayerConnectionDetails> PlayerConnectionDetails;
					
				for(const auto& LoopPlayer: GameStartData->Players)
				{
					FPragma_GameInstance_PlayerConnectionDetails MyPlayer;
					MyPlayer.PlayerId = LoopPlayer.PlayerId;
					PlayerConnectionDetails.Push(MyPlayer);

					PRAGMA_LOG(Log, "PragmaGameServer OnGameStart -- LoopPlayer Id: %s, TeamId %d", *LoopPlayer.PlayerId, LoopPlayer.TeamNumber);
				}

				UPragmaMatchApi::FOnCompleteDelegate OnCompleteDelegate = UPragmaMatchApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result3)
				{
					if (Result3.IsSuccessful())
					{
						UE_LOG(LogTemp, Display, TEXT("ConnectPlayers succeeded"));
					}
					else
					{
						UE_LOG(LogTemp, Display, TEXT("ConnectPlayers failed"));
					}
				});
				PragmaServer->MatchApi().ConnectMorePlayers(GameStartData->GameInstanceId, PlayerConnectionDetails, GameServerHostname, GameServerPort, OnCompleteDelegate); 
			} else
			{
				UE_LOG(LogTemp, Display, TEXT("Link failed"));
			}
		});;
		PragmaServer->MatchApi().Link(PragmaGameInstanceId, LinkDelegate);
	}
}

void APRGMSampleGameGameModeBase::ShutdownServer()
{
	PRAGMA_LOG(Log, "Shutting down server.");

	UAgonesSubsystem* Agones = UAgonesSubsystem::Get(this);
	if (Agones)
	{
		Agones->Shutdown({}, {});
	}

	FGenericPlatformMisc::RequestExit(false);
}

void APRGMSampleGameGameModeBase::OnPlayersAddedTriggered(FString GameInstanceId,
	TArray<FPragma_GameInstance_GameServerPlayer> GameServerPlayers)
{
	PRAGMA_LOG(Log, "Received notification that players have been added to the GameInstance. I need to call ConnectMorePlayers for them. PlayerIds: %s", *GameServerPlayers[0].PlayerId);
	TArray<FPragma_GameInstance_PlayerConnectionDetails> PlayerConnectionDetails;
	
	for(const auto& LoopPlayer: GameServerPlayers)
	{
		FPragma_GameInstance_PlayerConnectionDetails MyPlayer;
		MyPlayer.PlayerId = LoopPlayer.PlayerId;
		PlayerConnectionDetails.Push(MyPlayer);

		PRAGMA_LOG(Log, "PragmaGameServer OnGameStart -- LoopPlayer Id: %s, TeamId %d", *LoopPlayer.PlayerId, LoopPlayer.TeamNumber);
	}

	UPragmaMatchApi::FOnCompleteDelegate OnCompleteDelegate = UPragmaMatchApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("ConnectMorePlayers succeeded"));
		}
		else
		{
			UE_LOG(LogTemp, Display, TEXT("ConnectMorePlayers failed"));
		}
	});
	PragmaServer->MatchApi().ConnectMorePlayers(GameInstanceId, PlayerConnectionDetails, GameServerHostname, GameServerPort, OnCompleteDelegate); 
}
