// Copyright Pacificao. All Rights Reserved.

#include "AgrarianPersistenceSubsystem.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianGameState.h"
#include "AgrarianInventoryComponent.h"
#include "AgrarianPersistentActorComponent.h"
#include "AgrarianResourceNode.h"
#include "AgrarianSaveGame.h"
#include "AgrarianSurvivalComponent.h"
#include "EngineUtils.h"
#include "Engine/World.h"
#include "GameFramework/PlayerState.h"
#include "Kismet/GameplayStatics.h"

UAgrarianSaveGame* UAgrarianPersistenceSubsystem::CreateEmptySave() const
{
	return Cast<UAgrarianSaveGame>(UGameplayStatics::CreateSaveGameObject(UAgrarianSaveGame::StaticClass()));
}

UAgrarianSaveGame* UAgrarianPersistenceSubsystem::LoadOrCreateSave() const
{
	if (UGameplayStatics::DoesSaveGameExist(DefaultSlotName, UserIndex))
	{
		if (UAgrarianSaveGame* Loaded = Cast<UAgrarianSaveGame>(UGameplayStatics::LoadGameFromSlot(DefaultSlotName, UserIndex)))
		{
			return Loaded;
		}
	}

	return CreateEmptySave();
}

bool UAgrarianPersistenceSubsystem::WriteSave(UAgrarianSaveGame* SaveGame) const
{
	return SaveGame ? UGameplayStatics::SaveGameToSlot(SaveGame, DefaultSlotName, UserIndex) : false;
}

bool UAgrarianPersistenceSubsystem::DoesSaveExist() const
{
	return UGameplayStatics::DoesSaveGameExist(DefaultSlotName, UserIndex);
}

void UAgrarianPersistenceSubsystem::RegisterWorldActorClass(FName ActorTypeId, TSubclassOf<AActor> ActorClass)
{
	if (ActorTypeId != NAME_None && ActorClass)
	{
		WorldActorClassRegistry.Add(ActorTypeId, ActorClass);
	}
}

int32 UAgrarianPersistenceSubsystem::CaptureWorldActors(UAgrarianSaveGame* SaveGame) const
{
	if (!SaveGame)
	{
		return 0;
	}

	TArray<UAgrarianPersistentActorComponent*> PersistentComponents;
	FindPersistentComponents(PersistentComponents);

	SaveGame->WorldActors.Reset();
	for (const UAgrarianPersistentActorComponent* Component : PersistentComponents)
	{
		if (Component && Component->IsSaveable())
		{
			SaveGame->WorldActors.Add(Component->CaptureSaveState());
		}
	}

	return SaveGame->WorldActors.Num();
}

int32 UAgrarianPersistenceSubsystem::RestoreWorldActors(const UAgrarianSaveGame* SaveGame, bool bClearExistingActors) const
{
	UWorld* World = GetWorld();
	if (!World || !SaveGame)
	{
		return 0;
	}

	if (bClearExistingActors)
	{
		TArray<UAgrarianPersistentActorComponent*> ExistingComponents;
		FindPersistentComponents(ExistingComponents);

		for (UAgrarianPersistentActorComponent* Component : ExistingComponents)
		{
			if (AActor* Owner = Component ? Component->GetOwner() : nullptr)
			{
				Owner->Destroy();
			}
		}
	}

	int32 RestoredCount = 0;
	for (const FAgrarianSavedWorldActor& SavedActor : SaveGame->WorldActors)
	{
		const TSubclassOf<AActor>* ActorClass = WorldActorClassRegistry.Find(SavedActor.ActorTypeId);
		if (!ActorClass || !(*ActorClass))
		{
			continue;
		}

		FActorSpawnParameters SpawnParams;
		SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

		AActor* SpawnedActor = World->SpawnActor<AActor>(*ActorClass, SavedActor.Transform, SpawnParams);
		if (!SpawnedActor)
		{
			continue;
		}

		if (UAgrarianPersistentActorComponent* Component = SpawnedActor->FindComponentByClass<UAgrarianPersistentActorComponent>())
		{
			Component->ApplySaveState(SavedActor);
		}

		RestoredCount++;
	}

	return RestoredCount;
}

bool UAgrarianPersistenceSubsystem::CaptureWorldState(UAgrarianSaveGame* SaveGame) const
{
	UWorld* World = GetWorld();
	AAgrarianGameState* GameState = World ? World->GetGameState<AAgrarianGameState>() : nullptr;
	if (!SaveGame || !GameState)
	{
		return false;
	}

	SaveGame->WorldHours = GameState->WorldHours;
	SaveGame->Weather = GameState->Weather;
	SaveGame->WeatherInputs = GameState->ActiveWeatherInputs;
	SaveGame->WeatherDebug = GameState->GetWeatherDebugSnapshot();
	return true;
}

bool UAgrarianPersistenceSubsystem::RestoreWorldState(const UAgrarianSaveGame* SaveGame) const
{
	UWorld* World = GetWorld();
	AAgrarianGameState* GameState = World ? World->GetGameState<AAgrarianGameState>() : nullptr;
	if (!SaveGame || !GameState || !GameState->HasAuthority())
	{
		return false;
	}

	GameState->WorldHours = SaveGame->WorldHours;
	if (SaveGame->WeatherInputs.bHasProviderData)
	{
		GameState->ApplyMappedWeatherInputs(SaveGame->WeatherInputs);
	}
	else
	{
		GameState->SetWeather(SaveGame->Weather);
	}

	return true;
}

int32 UAgrarianPersistenceSubsystem::CapturePlayers(UAgrarianSaveGame* SaveGame) const
{
	if (!SaveGame)
	{
		return 0;
	}

	TArray<AAgrarianGameCharacter*> Players;
	FindAgrarianPlayers(Players);

	SaveGame->Players.Reset();
	for (const AAgrarianGameCharacter* Character : Players)
	{
		CapturePlayerIntoSave(Character, SaveGame);
	}

	return SaveGame->Players.Num();
}

int32 UAgrarianPersistenceSubsystem::RestorePlayers(const UAgrarianSaveGame* SaveGame) const
{
	if (!SaveGame)
	{
		return 0;
	}

	TArray<AAgrarianGameCharacter*> Players;
	FindAgrarianPlayers(Players);

	int32 RestoredCount = 0;
	for (AAgrarianGameCharacter* Character : Players)
	{
		if (RestorePlayerFromSave(Character, SaveGame))
		{
			RestoredCount++;
		}
	}

	return RestoredCount;
}

bool UAgrarianPersistenceSubsystem::SavePlayerSnapshot(const AAgrarianGameCharacter* Character) const
{
	UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
	if (!SaveGame || !CapturePlayerIntoSave(Character, SaveGame))
	{
		return false;
	}

	return WriteSave(SaveGame);
}

bool UAgrarianPersistenceSubsystem::RestorePlayerSnapshot(AAgrarianGameCharacter* Character) const
{
	const UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
	return SaveGame ? RestorePlayerFromSave(Character, SaveGame) : false;
}

int32 UAgrarianPersistenceSubsystem::CaptureResourceNodes(UAgrarianSaveGame* SaveGame) const
{
	if (!SaveGame)
	{
		return 0;
	}

	TArray<AAgrarianResourceNode*> ResourceNodes;
	FindResourceNodes(ResourceNodes);

	SaveGame->ResourceNodes.Reset();
	for (const AAgrarianResourceNode* ResourceNode : ResourceNodes)
	{
		if (!ResourceNode)
		{
			continue;
		}

		const FAgrarianSavedResourceNode SavedNode = ResourceNode->CaptureResourceSaveState();
		if (SavedNode.ResourceNodeId != NAME_None)
		{
			SaveGame->ResourceNodes.Add(SavedNode);
		}
	}

	return SaveGame->ResourceNodes.Num();
}

int32 UAgrarianPersistenceSubsystem::RestoreResourceNodes(const UAgrarianSaveGame* SaveGame) const
{
	if (!SaveGame)
	{
		return 0;
	}

	TArray<AAgrarianResourceNode*> ResourceNodes;
	FindResourceNodes(ResourceNodes);

	int32 RestoredCount = 0;
	for (AAgrarianResourceNode* ResourceNode : ResourceNodes)
	{
		if (!ResourceNode)
		{
			continue;
		}

		const FName ResourceNodeId = ResourceNode->GetResourcePersistenceId();
		const FAgrarianSavedResourceNode* SavedNode = SaveGame->ResourceNodes.FindByPredicate(
			[ResourceNodeId](const FAgrarianSavedResourceNode& Candidate)
			{
				return Candidate.ResourceNodeId == ResourceNodeId;
			});

		if (!SavedNode)
		{
			continue;
		}

		ResourceNode->ApplyResourceSaveState(*SavedNode);
		RestoredCount++;
	}

	return RestoredCount;
}

bool UAgrarianPersistenceSubsystem::SaveCurrentWorld() const
{
	UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
	if (!SaveGame)
	{
		return false;
	}

	CaptureWorldState(SaveGame);
	CapturePlayers(SaveGame);
	CaptureWorldActors(SaveGame);
	CaptureResourceNodes(SaveGame);
	return WriteSave(SaveGame);
}

bool UAgrarianPersistenceSubsystem::LoadCurrentWorld(int32& RestoredPlayerCount, int32& RestoredWorldActorCount, bool bClearExistingActors) const
{
	RestoredPlayerCount = 0;
	RestoredWorldActorCount = 0;

	const UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
	if (!SaveGame)
	{
		return false;
	}

	const bool bRestoredWorldState = RestoreWorldState(SaveGame);
	RestoredPlayerCount = RestorePlayers(SaveGame);
	RestoredWorldActorCount = RestoreWorldActors(SaveGame, bClearExistingActors);
	RestoreResourceNodes(SaveGame);
	return bRestoredWorldState;
}

bool UAgrarianPersistenceSubsystem::CapturePlayerIntoSave(const AAgrarianGameCharacter* Character, UAgrarianSaveGame* SaveGame) const
{
	const UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
	if (!Character || !SurvivalComponent || !SaveGame)
	{
		return false;
	}

	FAgrarianSavedPlayer SavedPlayer;
	SavedPlayer.PlayerId = GetPlayerPersistenceId(Character);
	SavedPlayer.Identity.StablePlayerId = SavedPlayer.PlayerId;
	SavedPlayer.Identity.LastKnownPawnName = Character->GetName();
	if (const APlayerState* PlayerState = Character->GetPlayerState())
	{
		SavedPlayer.Identity.PlayerName = PlayerState->GetPlayerName();
		const FUniqueNetIdRepl& UniqueId = PlayerState->GetUniqueId();
		if (UniqueId.IsValid())
		{
			SavedPlayer.Identity.NetworkId = UniqueId->ToString();
			SavedPlayer.Identity.bUsedNetworkId = true;
		}
	}
	SavedPlayer.Transform = Character->GetActorTransform();
	SavedPlayer.Survival = SurvivalComponent->Survival;
	SavedPlayer.CareHistory = SurvivalComponent->CareHistory;

	if (const UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
	{
		SavedPlayer.Inventory = InventoryComponent->Items;
	}

	SaveGame->Players.RemoveAll([&SavedPlayer](const FAgrarianSavedPlayer& Candidate)
	{
		return Candidate.PlayerId == SavedPlayer.PlayerId;
	});
	SaveGame->Players.Add(SavedPlayer);
	return true;
}

bool UAgrarianPersistenceSubsystem::RestorePlayerFromSave(AAgrarianGameCharacter* Character, const UAgrarianSaveGame* SaveGame) const
{
	UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
	if (!Character || !SurvivalComponent || !SaveGame)
	{
		return false;
	}

	const FString PlayerId = GetPlayerPersistenceId(Character);
	const FAgrarianSavedPlayer* SavedPlayer = SaveGame->Players.FindByPredicate(
		[&PlayerId](const FAgrarianSavedPlayer& Candidate)
		{
			return Candidate.PlayerId == PlayerId;
		});

	if (!SavedPlayer)
	{
		return false;
	}

	Character->SetActorTransform(SavedPlayer->Transform, false, nullptr, ETeleportType::TeleportPhysics);
	SurvivalComponent->ApplySavedState(SavedPlayer->Survival, SavedPlayer->CareHistory);

	if (UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
	{
		InventoryComponent->RestoreSavedItems(SavedPlayer->Inventory);
	}

	return true;
}

void UAgrarianPersistenceSubsystem::FindPersistentComponents(TArray<UAgrarianPersistentActorComponent*>& OutComponents) const
{
	OutComponents.Reset();

	UWorld* World = GetWorld();
	if (!World)
	{
		return;
	}

	for (TActorIterator<AActor> ActorIt(World); ActorIt; ++ActorIt)
	{
		AActor* Actor = *ActorIt;
		if (!Actor || Actor->IsPendingKillPending())
		{
			continue;
		}

		if (UAgrarianPersistentActorComponent* Component = Actor->FindComponentByClass<UAgrarianPersistentActorComponent>())
		{
			OutComponents.Add(Component);
		}
	}
}

void UAgrarianPersistenceSubsystem::FindAgrarianPlayers(TArray<AAgrarianGameCharacter*>& OutPlayers) const
{
	OutPlayers.Reset();

	UWorld* World = GetWorld();
	if (!World)
	{
		return;
	}

	for (TActorIterator<AAgrarianGameCharacter> ActorIt(World); ActorIt; ++ActorIt)
	{
		AAgrarianGameCharacter* Character = *ActorIt;
		if (Character && !Character->IsPendingKillPending())
		{
			OutPlayers.Add(Character);
		}
	}
}

void UAgrarianPersistenceSubsystem::FindResourceNodes(TArray<AAgrarianResourceNode*>& OutResourceNodes) const
{
	OutResourceNodes.Reset();

	UWorld* World = GetWorld();
	if (!World)
	{
		return;
	}

	for (TActorIterator<AAgrarianResourceNode> ActorIt(World); ActorIt; ++ActorIt)
	{
		AAgrarianResourceNode* ResourceNode = *ActorIt;
		if (ResourceNode && !ResourceNode->IsPendingKillPending())
		{
			OutResourceNodes.Add(ResourceNode);
		}
	}
}

FString UAgrarianPersistenceSubsystem::GetPlayerPersistenceId(const AAgrarianGameCharacter* Character) const
{
	if (!Character)
	{
		return TEXT("UnknownPlayer");
	}

	if (const APlayerState* PlayerState = Character->GetPlayerState())
	{
		const FUniqueNetIdRepl& UniqueId = PlayerState->GetUniqueId();
		if (UniqueId.IsValid())
		{
			return UniqueId->ToString();
		}

		const FString PlayerName = PlayerState->GetPlayerName();
		if (!PlayerName.IsEmpty())
		{
			return PlayerName;
		}
	}

	return Character->GetName();
}
