#include "MyPlayerController.h"

#include "PragmaGameServerSubsystem.h"
#include "PragmaPlayer.h"
#include "PragmaLocalPlayerSubsystem.h"
#include "PragmaServer.h"
#include "SampleGameGameModeBase.h"
#include "Blueprint/UserWidget.h"
#include "Components/Button.h"
#include "Components/EditableText.h"
#include "Components/TextBlock.h"
#include "Internationalization/Text.h"
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"

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

	if (!IsRunningDedicatedServer())
	{
		UE_LOG(LogTemp, Display, TEXT("Initializing Player Pragma SDK...") );

		const auto* PlayerSubsystem = GetLocalPlayer()->GetSubsystem<UPragmaLocalPlayerSubsystem>();

		// Player object represents the current player's session with the Pragma Backend and is 
		//created automatically by the `UPragmaLocalPlayerSubsystem`.
		PragmaPlayer = PlayerSubsystem->Player();

		if (PragmaPlayer->IsConnected())
		{
			SetupPragmaSubscriptions();
		}
		
		UE_LOG(LogTemp, Warning, TEXT("IsRunningDedicatedServer is false. We are player."));
		FindButtonsAndStuff();
	}
}

// -----------------------------------
// SETUP
// -----------------------------------

void AMyPlayerController::SetupPragmaSubscriptions()
{
	SetupPragmaPartyApiSubscriptions();
	SetupPragmaGameInstanceApiSubscriptions();
	SetupPragmaPlayerDataSubscriptions();
}

void AMyPlayerController::SetupPragmaGameInstanceApiSubscriptions()
{
	PragmaPlayer->GameInstanceApi().OnAddedToGameInstance.AddUObject(this, &AMyPlayerController::OnAddedToGameTriggered);
	PragmaPlayer->GameInstanceApi().OnHostConnectionDetailsReceived.AddUObject(this, &AMyPlayerController::OnHostConnectionDetailsReceivedTriggerred);
	PragmaPlayer->GameInstanceApi().OnGameInstanceEnded.AddUObject(this, &AMyPlayerController::OnGameEndedTriggerred);
}



void AMyPlayerController::SetupPragmaPartyApiSubscriptions()
{
	PragmaPlayer->PartyApi().OnPartyUpdated.AddUObject(this, &AMyPlayerController::OnPartyChangedTriggered);
	PragmaPlayer->PartyApi().OnEnteredMatchmaking.AddUObject(this, &AMyPlayerController::OnEnteredMatchmakingTriggered);
}


void AMyPlayerController::SetupPragmaPlayerDataSubscriptions()
{
	PragmaPlayer->PlayerDataService().OnPlayerDataUpdated.AddUObject(this, &AMyPlayerController::OnPlayerDataUpdated);
}

void AMyPlayerController::InitializePragmaApis()
{
	SetupPragmaPlayerDataSubscriptions();

	const UPragmaGameInstanceApi::FOnCompleteDelegate InitializeOnCompleteDelegate =
		UPragmaGameInstanceApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("GameLoopApi.InitializeOnCompleteDelegate successful."));
			SetupPragmaGameInstanceApiSubscriptions();
		} else
		{
			UE_LOG(LogTemp, Error, TEXT("GameLoopApi.InitializeOnCompleteDelegate failed."));
		}
	});
	PragmaPlayer->GameInstanceApi().Initialize(InitializeOnCompleteDelegate);


	const UPragmaPartyApi::FOnCompleteDelegate InitializePartyApiOnCompleteDelegate = UPragmaPartyApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("PartyApi.InitializePartyApiOnCompleteDelegate successful."));
			SetupPragmaPartyApiSubscriptions();
		} else
		{
			UE_LOG(LogTemp, Error, TEXT("PartyApi.InitializePartyApiOnCompleteDelegate failed."));
		}
	});
	PragmaPlayer->PartyApi().Initialize(InitializePartyApiOnCompleteDelegate);
	
	const FOnSyncVersionComplete InitializeCacheOnCompleteDelegate = FOnSyncVersionComplete::CreateLambda([&](const TOptional<EPlayerDataSyncError>& PlayerDataSyncError)
		{
			PRAGMA_LOG(Log, ">>>>>>>>>>>> Initializing cache")
			if (PlayerDataSyncError.IsSet())
			{
				PRAGMA_LOG(Error, ">>>>>> Could not retrieve Player Data Cache")
			} else
			{
				PRAGMA_LOG(Log, ">>>>>> Player data cache is initialized and we can now work with it.")
			}
		});
	PragmaPlayer->PlayerDataService().InitializeCache(InitializeCacheOnCompleteDelegate);
}

// -----------------------------------
// INTERFACE
// -----------------------------------

void AMyPlayerController::FindButtonsAndStuff()
{
	if (MainUIClass)
	{
		if (UUserWidget* MainUIInstance = CreateWidget<UUserWidget>(this, MainUIClass))
		{
			MainUIInstance->AddToViewport();

			// Ensure the widget is initialized before accessing its components
			MainUIInstance->SetVisibility(ESlateVisibility::Visible);

			// Logging to check if the instance is created
			UE_LOG(LogTemp, Display, TEXT("MainUIInstance created and added to viewport"));

			LoginButton = Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("LoginButton")));
			LoginSteamButton = Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("LoginSteamButton")));
			CrashAppButton = Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("CrashAppButton")));
			UsernameEditableTextBox = Cast<UEditableText>(MainUIInstance->GetWidgetFromName(TEXT("UsernameEditableTextBox")));
			CreatePartyButton =		  Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("CreatePartyButton")));
			SetPlayerIsReadyButton =  Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("SetPlayerIsReadyButton")));
			EnterMatchmakingButton =  Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("EnterMatchmakingButton")));
			CommunicateWithGameServerButton =  Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("CommunicateWithGameServerButton")));
			CommunicateMessageEditableTextBox = Cast<UEditableText>(MainUIInstance->GetWidgetFromName(TEXT("CommunicateMessageEditableTextBox")));
			ShowCurrencyButton =  Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("ShowCurrencyButton")));
			ShowCurrencyBalanceText =  Cast<UTextBlock>(MainUIInstance->GetWidgetFromName(TEXT("ShowCurrencyBalanceText")));
			GrantMeCurrencyButton =  Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("GrantMeCurrencyButton")));
			ShowListOfGameServersButton =  Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("ShowListOfGameServersButton")));
			JoinGameServerButton =  Cast<UButton>(MainUIInstance->GetWidgetFromName(TEXT("JoinGameServerButton")));
			GameInstanceIdEditableTextBox = Cast<UEditableText>(MainUIInstance->GetWidgetFromName(TEXT("GameInstanceIdEditableTextBox")));

			LoginButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnLoginButtonClicked);
			LoginSteamButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnLoginSteamButtonClicked);
			CrashAppButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnCrashAppButtonClicked);
			CreatePartyButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnCreatePartyButtonClicked);
			SetPlayerIsReadyButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnSetPlayerIsReadyButtonClicked);
			EnterMatchmakingButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnEnterMatchmakingButtonClicked);
			CommunicateWithGameServerButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnCommunicateWithGameServerButtonClicked);
			ShowCurrencyButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnShowPlayerCurrencyButtonClicked);
			GrantMeCurrencyButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnGrantMeCurrencyButtonClicked);
			ShowListOfGameServersButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnShowListOfGameServersButtonClicked);
			JoinGameServerButton->OnClicked.AddDynamic(this, &AMyPlayerController::OnJoinGameServerButtonClicked);
		}
		else
		{
			UE_LOG(LogTemp, Error, TEXT("Failed to create MainUIInstance"));
		}
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("MainUIClass is not set"));
	}
}

void AMyPlayerController::UpdatePlayerCurrencyBalance()
{
	if (PragmaPlayer->PlayerDataService().GetCache() == nullptr)
	{
		PRAGMA_LOG(Error, "PlayerData cache is not valid. Cannot perform this action");
		return;
	}
}

// -----------------------------------
// CLICKS
// -----------------------------------

void AMyPlayerController::OnCrashAppButtonClicked()
{
	UE_LOG(LogTemp, Display, TEXT("CLICKED ON CRASH APP. WILL SHUT DOWN.."));
	*((volatile int*)NULL) = 123;
}

void AMyPlayerController::OnLoginButtonClicked()
{
	FString Username = UsernameEditableTextBox->GetText().ToString();
	UE_LOG(LogTemp, Display, TEXT("Clicked on the login button with username %s. Will LogIn"), *Username);
	LogIn(Username);
}

void AMyPlayerController::OnLoginSteamButtonClicked()
{
	UE_LOG(LogTemp, Display, TEXT("Clicked on the login steam button. Will LogIn"));
	LogInSteam();
}

void AMyPlayerController::OnCreatePartyButtonClicked()
{
	UE_LOG(LogTemp, Display, TEXT("CLICKED ON CREATE PARTY."));
	const UPragmaPartyApi::FOnCompleteDelegate OnCreatePartyCompleteDelegate = UPragmaPartyApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("Create party delegate successful."));
		} else
		{
			UE_LOG(LogTemp, Error, TEXT("Create party delegate failed."));
		}
	});
	
	PragmaPlayer->PartyApi().CreateParty(FPragma_Party_ExtCreateRequest{}, FPragma_Party_ExtPlayerJoinRequest{}, OnCreatePartyCompleteDelegate);
}

void AMyPlayerController::OnSetPlayerIsReadyButtonClicked()
{
	UE_LOG(LogTemp, Display, TEXT("CLICKED ON IS READY."));
	const UPragmaPartyApi::FOnCompleteDelegate OnSetPlayerIsReadyCompleteDelegate = UPragmaPartyApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("OnSetPlayerIsReadyCompleteDelegate successful."));
		} else
		{
			UE_LOG(LogTemp, Error, TEXT("OnSetPlayerIsReadyCompleteDelegate failed."));
		}
	});
	PragmaPlayer->PartyApi().SetPartyPlayerReady(true, OnSetPlayerIsReadyCompleteDelegate);
}

void AMyPlayerController::OnEnterMatchmakingButtonClicked()
{
	UE_LOG(LogTemp, Display, TEXT("CLICKED ON ENTERMATCHMAKING"));
	const UPragmaPartyApi::FOnCompleteDelegate EnterMatchmakingCompleteDelegate = UPragmaPartyApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result)
	{
		if (Result.IsSuccessful())
		{
			UE_LOG(LogTemp, Display, TEXT("EnterMatchmakingCompleteDelegate successful."));
		} else
		{
			UE_LOG(LogTemp, Error, TEXT("EnterMatchmakingCompleteDelegate failed."));
		}
	});
	PragmaPlayer->PartyApi().EnterMatchmaking(EnterMatchmakingCompleteDelegate);
}

void AMyPlayerController::OnCommunicateWithGameServerButtonClicked()
{
	FString Message = CommunicateMessageEditableTextBox->GetText().ToString();
	AMyPlayerController::CommunicateWithGameServer(Message);
}

void AMyPlayerController::OnShowPlayerCurrencyButtonClicked()
{
	UpdatePlayerCurrencyBalance();
}

void AMyPlayerController::OnGrantMeCurrencyButtonClicked()
{
	AMyPlayerController::GrantPlayerCurrencyGameServer(PragmaPlayer->Account().Player()->Id());
}

void AMyPlayerController::OnShowListOfGameServersButtonClicked()
{
}

void AMyPlayerController::OnJoinGameServerButtonClicked()
{
	FString GameInstanceId = GameInstanceIdEditableTextBox->GetText().ToString();
	UPragmaGameInstanceApi::FOnCompleteDelegate JoinDelegate = UPragmaGameInstanceApi::FOnCompleteDelegate::CreateLambda([&](const TPragmaResult<>& Result)
	{
		if (Result.IsSuccessful())
		{
			PRAGMA_LOG(Log, "You Successfuly joined gameinstance with id %s.", *GameInstanceId);
		} else
		{
			PRAGMA_LOG(Error, "Error when joining gameinstance with id %s.", *GameInstanceId);
		}
	});
	PragmaPlayer->GameInstanceApi().Join(GameInstanceId, FPragma_GameInstance_ExtPlayerJoinGameInstanceRequest{}, JoinDelegate);
}

// -----------------------------------
// GAME SERVER IMPLEMENTATIONS
// -----------------------------------

void AMyPlayerController::CommunicateWithGameServer_Implementation(const FString& Message)
{
	if (IsRunningDedicatedServer())
	{
		
		UE_LOG(LogTemp, Warning, TEXT("Message received from Game Client %s. If 'IWON' received, I will end the game"), *Message);
		if (Message == "IWON")
		{
			Cast<APRGMSampleGameGameModeBase>(GetWorld()->GetAuthGameMode())->EndGameMethod();
		} else {
			UE_LOG(LogTemp, Warning, TEXT("Message received from Game Client %s. I will not end the game"), *Message);
		}
	} else {
		UE_LOG(LogTemp, Warning, TEXT("You clicked on the CommunicateWithGameServerButton while not connected to server."));
	}
}

void AMyPlayerController::GrantPlayerCurrencyGameServer_Implementation(const FString& PlayerId)
{
	if (!IsRunningDedicatedServer()) {
		PRAGMA_LOG(Warning, "You clicked on the GrantPlayerCurrencyGameServer while not connected to server.");
		return;
	}

	Cast<APRGMSampleGameGameModeBase>(GetWorld()->GetAuthGameMode())->GrantCurrency(PlayerId);
}


// -----------------------------------
// SUBSCRIPTIONS
// -----------------------------------

void AMyPlayerController::OnPartyChangedTriggered(const FPragmaParty& PragmaParty)
{
	UE_LOG(LogTemp, Display, TEXT("This party was changed: %s."), *PragmaParty.GetId());
}

void AMyPlayerController::OnEnteredMatchmakingTriggered()
{
	UE_LOG(LogTemp, Display, TEXT("Entered matchmaking"));
}

void AMyPlayerController::OnAddedToGameTriggered(const FPragmaGameInstance& PragmaGameInstance)
{
	UE_LOG(LogTemp, Display, TEXT("Added to game with id: %s."), *PragmaGameInstance.GetId());

}

void AMyPlayerController::OnHostConnectionDetailsReceivedTriggerred(const FPragmaGameInstance& PragmaGameInstance)
{
	TOptional<FPragmaHostConnectionDetails> HostConnectionDetails = PragmaGameInstance.GetHostConnectionDetails();
	PRAGMA_LOG(Log, "Player should connect to host");

	if (HostConnectionDetails.IsSet())
	{
		// Connect to game server logic
		const FString ConnectionIp = HostConnectionDetails->GetHostName();
		const int ConnectionPort = HostConnectionDetails->GetPort();

		const FString Endpoint = ConnectionIp + ":" + FString::FromInt(ConnectionPort);
		const FString Command = "open "  + Endpoint;
		GEngine->Exec(GetWorld(), *Command);
	}
}

void AMyPlayerController::OnGameEndedTriggerred(const FString& GameInstanceId, const FPragma_GameInstance_ExtGameEnded& Pragma_GameInstance_ExtGameEnded)
{
	PRAGMA_LOG(Warning, "Game Client. Player's game has ended. GameInstanceId was %s", *GameInstanceId);
}

void AMyPlayerController::OnPlayerDataUpdated()
{
	PRAGMA_LOG(Warning, "Game Client. OnPlayerDataUpdated was triggered. PlayerDataCache should already have been updated.");
	PRAGMA_LOG(Warning, "Game Client. We'll pretend that we clicked the ShowExperienceButton.");
	OnShowPlayerCurrencyButtonClicked();
}

void AMyPlayerController::OnPlayerDataUpdatedV1(FPragma_PlayerData_PlayerDataUpdatedV1Notification Notification,
	const FPragmaMessageMetadata& PragmaMessageMetadata)
{
	
}

void AMyPlayerController::OnReconnectedTriggered()
{
	
}


// -----------------------------------
// METHODS
// -----------------------------------

void AMyPlayerController::LogIn(const FString& Username)
{
	PragmaPlayer->LogIn(
		EPragma_Account_IdProvider::UNSAFE,
		Username,
		Pragma::FPlayer::FLoggedInDelegate::CreateUObject(
			this, &AMyPlayerController::HandleLoggedIn));
}

void AMyPlayerController::LogOut()
{
	PragmaPlayer->LogOut(Pragma::FPlayer::FLoggedOutDelegate::CreateUObject(
		this, &AMyPlayerController::HandleLoggedOut));
}


// -----------------------------------
// HANDLERS
// -----------------------------------

void AMyPlayerController::HandleLoggedIn(const TPragmaResult<>& Result)
{
	if (Result.IsFailure())
	{
		UE_LOG(LogTemp, Display, TEXT("Pragma -- Login failed: %s"), *Result.ErrorCode());
		return;
	}
	UE_LOG(LogTemp, Display, TEXT("Pragma -- Logged in."));
	InitializePragmaApis();
}

void AMyPlayerController::HandleLoggedOut()
{
	UE_LOG(LogTemp, Display, TEXT("Pragma -- Logged out."));
}

void AMyPlayerController::LogInSteam()
{
	PragmaPlayer->LogIn(
	  EPragma_Account_IdProvider::STEAM,
	  GetSteamToken(),
	  Pragma::FPlayer::FLoggedInDelegate::CreateWeakLambda(
		this, [this](const TPragmaResult<>& Result)
		{
		  if (Result.IsSuccessful())
		  {
			UE_LOG(LogTemp, Display, TEXT("MyGameClient -- Logged in with Steam."));
		  }
		  else
		  {
			UE_LOG(LogTemp, Error, TEXT("MyGameClient -- Steam login failed: %s"),
				  *Result.GetErrorAsString());
		  }
		}));
}

FString AMyPlayerController::GetSteamToken()
{
	IOnlineSubsystem* OSS = Online::GetSubsystem(GetWorld());
	return OSS->GetIdentityInterface()->GetAuthToken(0);
}
