#include "HyperTwistXR/HyperTwistXrPackageValidationLibrary.h"

#include "HAL/FileManager.h"
#include "HyperTwistXR/HyperTwistXrRuntimeLibrary.h"
#include "HyperTwistXR/HyperTwistXrTrainingPawn.h"
#include "JsonObjectConverter.h"
#include "Misc/DateTime.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"

namespace HyperTwistXrPackageValidationLibraryInternal
{
	const TCHAR* DefaultCookMapPath =
		TEXT("/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining");
	const TCHAR* ValidationLaunchSurfaceId =
		TEXT("xr/openxr-desktop-training-packaged-validation-launch-surface");
	const TCHAR* ValidationGameModeClassPath =
		TEXT("/Script/UnrealHyperTwist.HyperTwistXrTrainingGameMode");
	const TCHAR* PendingHmdSessionObservationStatus =
		TEXT("pending-hmd-session-observation");
	const TCHAR* PendingControllerInputObservationStatus =
		TEXT("pending-controller-input-observation");
	const TCHAR* ObservedLiveControllerInputStatus =
		TEXT("observed-live-controller-input");
	const TCHAR* RemainingHmdSessionGateId =
		TEXT("xr/windows-packaged-headset-session-observation");
	const TCHAR* RemainingControllerInputGateId =
		TEXT("xr/windows-packaged-controller-input-observation");

	FString ResolveProofStatus(
		const bool bObservedHeadMountedDisplaySession,
		const bool bObservedControllerInputActivity
	)
	{
		if (!bObservedHeadMountedDisplaySession)
		{
			return PendingHmdSessionObservationStatus;
		}

		if (!bObservedControllerInputActivity)
		{
			return PendingControllerInputObservationStatus;
		}

		return ObservedLiveControllerInputStatus;
	}

	void PopulateRemainingGateIds(
		TArray<FString>& OutRemainingGateIds,
		const bool bObservedHeadMountedDisplaySession,
		const bool bObservedControllerInputActivity
	)
	{
		OutRemainingGateIds.Reset();
		if (!bObservedHeadMountedDisplaySession)
		{
			OutRemainingGateIds.Add(RemainingHmdSessionGateId);
		}

		if (!bObservedControllerInputActivity)
		{
			OutRemainingGateIds.Add(RemainingControllerInputGateId);
		}
	}
}

bool FHyperTwistXrPackagedValidationReport::IsStructurallyValid() const
{
	return !ReportVersion.IsEmpty()
		&& !GeneratedAtUtc.IsEmpty()
		&& !LaunchSurfaceId.IsEmpty()
		&& !GameModeClassPath.IsEmpty()
		&& !CookMapAssetPath.IsEmpty()
		&& !LaunchMapUrl.IsEmpty()
		&& !Result.IsEmpty()
		&& !PackagedProofStatus.IsEmpty()
		&& !RuntimeOwnerId.IsEmpty()
		&& !PreferenceProfileId.IsEmpty()
		&& !ControllerBindingProfileId.IsEmpty()
		&& !ControllerSettingsOwnerId.IsEmpty()
		&& ObservationSeconds >= 0.0;
}

FString UHyperTwistXrPackageValidationLibrary::GetDefaultPackagedValidationCookMapPath()
{
	return HyperTwistXrPackageValidationLibraryInternal::DefaultCookMapPath;
}

FString UHyperTwistXrPackageValidationLibrary::GetValidationLaunchSurfaceId()
{
	return HyperTwistXrPackageValidationLibraryInternal::ValidationLaunchSurfaceId;
}

FString UHyperTwistXrPackageValidationLibrary::GetValidationGameModeClassPath()
{
	return HyperTwistXrPackageValidationLibraryInternal::ValidationGameModeClassPath;
}

FString UHyperTwistXrPackageValidationLibrary::BuildPackagedValidationMapUrl(
	const FString& CookMapAssetPath
)
{
	const FString ResolvedCookMapAssetPath = CookMapAssetPath.IsEmpty()
		? GetDefaultPackagedValidationCookMapPath()
		: CookMapAssetPath;
	return FString::Printf(
		TEXT("%s?game=%s"),
		*ResolvedCookMapAssetPath,
		*GetValidationGameModeClassPath()
	);
}

FHyperTwistXrPackagedValidationReport
UHyperTwistXrPackageValidationLibrary::BuildPackagedValidationReport(
	const AHyperTwistXrTrainingPawn* TrainingPawn,
	const FString& CookMapAssetPath,
	const FString& ReportPath,
	const double ObservationSeconds,
	const bool bObservedHeadMountedDisplaySession,
	const bool bObservedControllerInputActivity,
	const float ObservedControllerInputMagnitude
)
{
	const FString ResolvedCookMapAssetPath = CookMapAssetPath.IsEmpty()
		? GetDefaultPackagedValidationCookMapPath()
		: CookMapAssetPath;

	const FHyperTwistXrRuntimeOwnershipSurface RuntimeOwnershipSurface =
		TrainingPawn != nullptr
			? TrainingPawn->GetBoundedRuntimeOwnershipSurface()
			: UHyperTwistXrRuntimeLibrary::BuildBundledXrRuntimeOwnershipSurface();
	const FHyperTwistXrPreferenceProfile PreferenceProfile =
		TrainingPawn != nullptr
			? TrainingPawn->ActivePreferenceProfile
			: UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile();
	const FHyperTwistXrControllerBindingProfile ControllerBindingProfile =
		TrainingPawn != nullptr
			? TrainingPawn->ActiveControllerBindingProfile
			: UHyperTwistXrRuntimeLibrary::GetDefaultXrControllerBindingProfile();
	const FHyperTwistXrControllerSettingsOwnershipSurface ControllerSettingsSurface =
		UHyperTwistXrRuntimeLibrary::BuildBundledXrControllerSettingsOwnershipSurface();

	FHyperTwistXrPackagedValidationReport Report;
	Report.GeneratedAtUtc = FDateTime::UtcNow().ToIso8601();
	Report.LaunchSurfaceId = GetValidationLaunchSurfaceId();
	Report.GameModeClassPath = GetValidationGameModeClassPath();
	Report.CookMapAssetPath = ResolvedCookMapAssetPath;
	Report.LaunchMapUrl = BuildPackagedValidationMapUrl(ResolvedCookMapAssetPath);
	Report.ReportPath = ReportPath;
	Report.ObservationSeconds = FMath::Max(ObservationSeconds, 0.0);
	Report.RuntimeOwnerId = RuntimeOwnershipSurface.RuntimeOwnerId;
	Report.PreferenceProfileId = PreferenceProfile.ProfileId;
	Report.ControllerBindingProfileId = ControllerBindingProfile.ProfileId;
	Report.ControllerSettingsOwnerId = ControllerSettingsSurface.SettingsOwnerId;
	Report.RuntimeStatusLine = TrainingPawn != nullptr
		? TrainingPawn->BuildRuntimeStatusLine()
		: TEXT("xr training pawn unavailable during packaged validation capture");
	Report.bRuntimeOwnerStructurallyValid = RuntimeOwnershipSurface.IsStructurallyValid();
	Report.bControllerSettingsOwnerStructurallyValid =
		ControllerSettingsSurface.IsStructurallyValid();
	Report.bHeadMountedDisplayConnected =
		TrainingPawn != nullptr && TrainingPawn->bHeadMountedDisplayConnected;
	Report.bHeadMountedDisplayEnabled =
		TrainingPawn != nullptr && TrainingPawn->bHeadMountedDisplayEnabled;
	Report.bObservedHeadMountedDisplaySession = bObservedHeadMountedDisplaySession;
	Report.bObservedControllerInputActivity = bObservedControllerInputActivity;
	Report.ObservedControllerInputMagnitude = FMath::Max(ObservedControllerInputMagnitude, 0.0f);
	Report.PackagedProofStatus =
		HyperTwistXrPackageValidationLibraryInternal::ResolveProofStatus(
			bObservedHeadMountedDisplaySession,
			bObservedControllerInputActivity
		);
	HyperTwistXrPackageValidationLibraryInternal::PopulateRemainingGateIds(
		Report.RemainingGateIds,
		bObservedHeadMountedDisplaySession,
		bObservedControllerInputActivity
	);

	if (TrainingPawn == nullptr)
	{
		Report.Error = TEXT("XR training pawn was unavailable during packaged validation capture.");
	}
	else if (!Report.bRuntimeOwnerStructurallyValid)
	{
		Report.Error = TEXT("The XR runtime-owner surface was not structurally valid during packaged validation capture.");
	}
	else if (!PreferenceProfile.IsStructurallyValid())
	{
		Report.Error = TEXT("The XR preference profile was not structurally valid during packaged validation capture.");
	}
	else if (!ControllerBindingProfile.IsStructurallyValid())
	{
		Report.Error = TEXT("The XR controller binding profile was not structurally valid during packaged validation capture.");
	}
	else if (!Report.bControllerSettingsOwnerStructurallyValid)
	{
		Report.Error = TEXT("The XR controller settings ownership surface was not structurally valid during packaged validation capture.");
	}
	else
	{
		Report.Result = TEXT("passed");
	}

	return Report;
}

bool UHyperTwistXrPackageValidationLibrary::WritePackagedValidationReportToFile(
	const FHyperTwistXrPackagedValidationReport& Report,
	const FString& ReportPath,
	FString& OutError
)
{
	OutError.Reset();
	if (ReportPath.IsEmpty())
	{
		OutError = TEXT("Packaged validation report path was empty.");
		return false;
	}

	const FString ParentDirectory = FPaths::GetPath(ReportPath);
	if (!ParentDirectory.IsEmpty()
		&& !IFileManager::Get().MakeDirectory(*ParentDirectory, true))
	{
		OutError = FString::Printf(
			TEXT("Failed to create packaged validation report directory '%s'."),
			*ParentDirectory
		);
		return false;
	}

	FString ReportJson;
	if (!FJsonObjectConverter::UStructToJsonObjectString(
			FHyperTwistXrPackagedValidationReport::StaticStruct(),
			&Report,
			ReportJson,
			0,
			0))
	{
		OutError = TEXT("Failed to serialize the XR packaged validation report to JSON.");
		return false;
	}

	if (!FFileHelper::SaveStringToFile(
			ReportJson,
			*ReportPath,
			FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM))
	{
		OutError = FString::Printf(
			TEXT("Failed to write the XR packaged validation report to '%s'."),
			*ReportPath
		);
		return false;
	}

	return true;
}
