// Copyright Epic Games, Inc. All Rights Reserved.
// UE5 Modular Game Framework — SS_SaveManager Implementation

#include "Save/SS_SaveManager.h"
#include "Core/GI_GameFramework.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "HAL/PlatformFileManager.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/MemoryWriter.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"

DEFINE_LOG_CATEGORY_STATIC(LogSave, Log, All);

USS_SaveManager::USS_SaveManager()
{
	MaxSlots = 10;
	SavePrefix = TEXT("FrameworkSave_");
}

// ============================================================================
// Lifecycle
// ============================================================================

void USS_SaveManager::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	UE_LOG(LogSave, Log, TEXT("SS_SaveManager::Initialize — Save directory: %s"), *GetSaveDirectory());

	// Ensure save directory exists.
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	if (!PlatformFile.DirectoryExists(*GetSaveDirectory()))
	{
		PlatformFile.CreateDirectoryTree(*GetSaveDirectory());
	}

	// Broadcast initial manifest.
	OnSaveManifestUpdated.Broadcast(GetSlotManifest());
}

void USS_SaveManager::Deinitialize()
{
	UE_LOG(LogSave, Log, TEXT("SS_SaveManager::Deinitialize"));
	Super::Deinitialize();
}

// ============================================================================
// Slot Manifest
// ============================================================================

TArray<FSaveSlotInfo> USS_SaveManager::GetSlotManifest() const
{
	TArray<FSaveSlotInfo> Manifest;

	for (int32 i = 0; i < MaxSlots; ++i)
	{
		FSaveSlotInfo Info = ReadSlotHeader(i);
		Info.SlotIndex = i;
		Manifest.Add(Info);
	}

	return Manifest;
}

bool USS_SaveManager::DoesSlotExist(int32 SlotIndex) const
{
	if (SlotIndex < 0 || SlotIndex >= MaxSlots)
	{
		return false;
	}

	FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
	return PlatformFile.FileExists(*FilePath);
}

// ============================================================================
// Save / Load Operations
// ============================================================================

bool USS_SaveManager::SaveGame(int32 SlotIndex, const FString& Description)
{
	if (SlotIndex < 0 || SlotIndex >= MaxSlots)
	{
		UE_LOG(LogSave, Warning, TEXT("SaveGame — Invalid slot index: %d"), SlotIndex);
		return false;
	}

	UE_LOG(LogSave, Log, TEXT("SaveGame — Slot %d: '%s'"), SlotIndex, *Description);

	// Build metadata.
	FSaveSlotInfo Meta;
	Meta.SlotIndex = SlotIndex;
	Meta.SlotName = FString::Printf(TEXT("Slot %d"), SlotIndex);
	Meta.ChapterName = Description; // Simplified — real impl gets chapter from GS_CoreGameState.
	Meta.Timestamp = FDateTime::Now();

	// Serialize game state to binary buffer.
	// In a full implementation, this calls I_Persistable::OnSave() on all registered actors.
	TArray<uint8> SaveData;
	FMemoryWriter Writer(SaveData);
	FObjectAndNameAsStringProxyArchive Ar(Writer, true);
	// Ar << GameStateData...

	bool bSuccess = SaveToFile(SlotIndex, SaveData, Meta);

	OnSaveComplete.Broadcast(SlotIndex, bSuccess);
	OnSaveManifestUpdated.Broadcast(GetSlotManifest());

	return bSuccess;
}

bool USS_SaveManager::LoadGame(int32 SlotIndex)
{
	if (!DoesSlotExist(SlotIndex))
	{
		UE_LOG(LogSave, Warning, TEXT("LoadGame — Slot %d does not exist"), SlotIndex);
		OnLoadComplete.Broadcast(SlotIndex, false);
		return false;
	}

	UE_LOG(LogSave, Log, TEXT("LoadGame — Slot %d"), SlotIndex);

	TArray<uint8> SaveData;
	FSaveSlotInfo Meta;

	if (!LoadFromFile(SlotIndex, SaveData, Meta))
	{
		UE_LOG(LogSave, Error, TEXT("LoadGame — Failed to read slot %d from disk"), SlotIndex);
		OnLoadComplete.Broadcast(SlotIndex, false);
		return false;
	}

	// Deserialize game state.
	FMemoryReader Reader(SaveData);
	FObjectAndNameAsStringProxyArchive Ar(Reader, true);
	// Ar << GameStateData...

	// Set active slot in GameInstance.
	UGI_GameFramework* GI = Cast<UGI_GameFramework>(GetGameInstance());
	if (GI)
	{
		GI->SetActiveSlot(SlotIndex);
	}

	OnLoadComplete.Broadcast(SlotIndex, true);

	return true;
}

bool USS_SaveManager::DeleteSlot(int32 SlotIndex)
{
	if (!DoesSlotExist(SlotIndex))
	{
		UE_LOG(LogSave, Warning, TEXT("DeleteSlot — Slot %d does not exist"), SlotIndex);
		return false;
	}

	FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

	bool bDeleted = PlatformFile.DeleteFile(*FilePath);

	UE_LOG(LogSave, Log, TEXT("DeleteSlot — Slot %d: %s"), SlotIndex, bDeleted ? TEXT("Deleted") : TEXT("Failed"));

	OnSaveManifestUpdated.Broadcast(GetSlotManifest());

	return bDeleted;
}

bool USS_SaveManager::QuickSave()
{
	int32 Slot = GetActiveSlot();
	if (Slot < 0)
	{
		UE_LOG(LogSave, Warning, TEXT("QuickSave — No active slot!"));
		return false;
	}

	return SaveGame(Slot, TEXT("QuickSave"));
}

bool USS_SaveManager::QuickLoad()
{
	int32 Slot = GetActiveSlot();
	if (Slot < 0)
	{
		UE_LOG(LogSave, Warning, TEXT("QuickLoad — No active slot!"));
		return false;
	}

	return LoadGame(Slot);
}

// ============================================================================
// Checkpoint Management
// ============================================================================

bool USS_SaveManager::LoadCheckpoint(int32 SlotIndex)
{
	// Checkpoints are incremental saves within a slot.
	// For now, loads the full slot (same as LoadGame).
	UE_LOG(LogSave, Log, TEXT("LoadCheckpoint — Slot %d"), SlotIndex);
	return LoadGame(SlotIndex);
}

bool USS_SaveManager::CreateCheckpoint(FGameplayTag CheckpointTag)
{
	int32 Slot = GetActiveSlot();
	if (Slot < 0)
	{
		UE_LOG(LogSave, Warning, TEXT("CreateCheckpoint — No active slot!"));
		return false;
	}

	UE_LOG(LogSave, Log, TEXT("CreateCheckpoint — Slot %d, Tag: %s"),
		Slot, *CheckpointTag.GetTagName().ToString());

	return SaveGame(Slot, FString::Printf(TEXT("Checkpoint: %s"), *CheckpointTag.GetTagName().ToString()));
}

// ============================================================================
// Utilities
// ============================================================================

int64 USS_SaveManager::GetTotalSaveSize() const
{
	int64 TotalSize = 0;
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

	for (int32 i = 0; i < MaxSlots; ++i)
	{
		FString FilePath = GetSaveDirectory() / GetSlotName(i) + TEXT(".sav");
		if (PlatformFile.FileExists(*FilePath))
		{
			TotalSize += PlatformFile.FileSize(*FilePath);
		}
	}

	return TotalSize;
}

bool USS_SaveManager::BackupAllSaves(const FString& BackupLabel)
{
	FString BackupDir = GetSaveDirectory() / TEXT("Backups") / BackupLabel;
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

	if (!PlatformFile.DirectoryExists(*BackupDir))
	{
		PlatformFile.CreateDirectoryTree(*BackupDir);
	}

	int32 BackedUp = 0;
	for (int32 i = 0; i < MaxSlots; ++i)
	{
		FString SrcPath = GetSaveDirectory() / GetSlotName(i) + TEXT(".sav");
		FString DstPath = BackupDir / GetSlotName(i) + TEXT(".sav");

		if (PlatformFile.FileExists(*SrcPath))
		{
			if (PlatformFile.CopyFile(*DstPath, *SrcPath))
			{
				++BackedUp;
			}
		}
	}

	UE_LOG(LogSave, Log, TEXT("BackupAllSaves — '%s': %d slots backed up"), *BackupLabel, BackedUp);
	return BackedUp > 0;
}

// ============================================================================
// Internal
// ============================================================================

FString USS_SaveManager::GetSlotName(int32 SlotIndex) const
{
	return FString::Printf(TEXT("%s%d"), *SavePrefix, SlotIndex);
}

FString USS_SaveManager::GetSaveDirectory() const
{
	return FPaths::ProjectSavedDir() / TEXT("SaveGames");
}

int32 USS_SaveManager::GetActiveSlot() const
{
	UGI_GameFramework* GI = Cast<UGI_GameFramework>(GetGameInstance());
	return GI ? GI->ActiveSlotIndex : -1;
}

FSaveSlotInfo USS_SaveManager::ReadSlotHeader(int32 SlotIndex) const
{
	FSaveSlotInfo Info;
	Info.SlotIndex = SlotIndex;

	FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
	IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();

	if (!PlatformFile.FileExists(*FilePath))
	{
		Info.bIsEmpty = true;
		return Info;
	}

	// Read header only — fast, doesn't deserialize the full save.
	TArray<uint8> FileData;
	if (FFileHelper::LoadFileToArray(FileData, *FilePath))
	{
		// Simplified header parsing — real impl reads a FSaveSlotInfo struct prefix.
		Info.bIsEmpty = false;
		Info.SlotName = FString::Printf(TEXT("Slot %d"), SlotIndex);
		Info.Timestamp = PlatformFile.GetTimeStamp(*FilePath);
		// In full impl: deserialize header from FileData.
	}

	return Info;
}

bool USS_SaveManager::SaveToFile(int32 SlotIndex, const TArray<uint8>& Data, const FSaveSlotInfo& Meta)
{
	FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");

	// Serialize metadata header + save data.
	TArray<uint8> FileData;
	FMemoryWriter Writer(FileData);

	// Write metadata header first.
	FSaveSlotInfo& MetaRef = const_cast<FSaveSlotInfo&>(Meta);
	FSaveSlotInfo::StaticStruct()->SerializeItem(Writer, &MetaRef, nullptr);

	// Then write game state data.
	Writer.Serialize(const_cast<uint8*>(Data.GetData()), Data.Num());

	bool bSaved = FFileHelper::SaveArrayToFile(FileData, *FilePath);

	if (bSaved)
	{
		UE_LOG(LogSave, Log, TEXT("SaveToFile — Slot %d: %d bytes written"), SlotIndex, FileData.Num());
	}
	else
	{
		UE_LOG(LogSave, Error, TEXT("SaveToFile — Slot %d: FAILED to write!"), SlotIndex);
	}

	return bSaved;
}

bool USS_SaveManager::LoadFromFile(int32 SlotIndex, TArray<uint8>& OutData, FSaveSlotInfo& OutMeta)
{
	FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");

	TArray<uint8> FileData;
	if (!FFileHelper::LoadFileToArray(FileData, *FilePath))
	{
		return false;
	}

	// Deserialize metadata header.
	FMemoryReader Reader(FileData);
	FSaveSlotInfo::StaticStruct()->SerializeItem(Reader, &OutMeta, nullptr);

	// Remaining bytes are game state data.
	int32 HeaderSize = Reader.Tell();
	OutData.SetNum(FileData.Num() - HeaderSize);
	FMemory::Memcpy(OutData.GetData(), FileData.GetData() + HeaderSize, OutData.Num());

	UE_LOG(LogSave, Log, TEXT("LoadFromFile — Slot %d: %d bytes read"), SlotIndex, FileData.Num());

	return true;
}
