// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "Components/SceneComponent.h"
#include "Math/UnrealMathUtility.h"

#include "HyperTwistXR/HyperTwistXrRuntimeLibrary.h"
#include "HyperTwistXR/HyperTwistXrTrainingPawn.h"

#if WITH_AUTOMATION_TESTS

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistXrRuntimeOwnershipSurfaceTest,
	"HyperTwist.FirstParty.XR.RuntimeOwnershipSurface",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistXrRuntimeOwnershipSurfaceTest::RunTest(const FString& Parameters)
{
	const FHyperTwistXrRuntimeOwnershipSurface Surface =
		UHyperTwistXrRuntimeLibrary::BuildBundledXrRuntimeOwnershipSurface();
	TestTrue(
		TEXT("The bounded XR runtime ownership surface must be structurally valid."),
		Surface.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The bounded XR runtime ownership id must stay stable."),
		Surface.RuntimeOwnerId,
		FString(TEXT("xr/openxr-desktop-training-runtime-owner"))
	);
	TestEqual(
		TEXT("The bounded XR runtime ownership surface must keep the left motion source stable."),
		Surface.LeftMotionSourceId,
		FString(TEXT("Left"))
	);
	TestEqual(
		TEXT("The bounded XR runtime ownership surface must keep the right motion source stable."),
		Surface.RightMotionSourceId,
		FString(TEXT("Right"))
	);
	TestTrue(
		TEXT("The bounded XR runtime ownership surface must preserve the first-party interaction tags."),
		Surface.RequiredSurfaceTags.Contains(TEXT("motion-controller pose anchors"))
			&& Surface.RequiredSurfaceTags.Contains(TEXT("camera-aligned locomotion vector"))
			&& Surface.RequiredSurfaceTags.Contains(TEXT("training-world recenter"))
	);
	TestTrue(
		TEXT("The bounded XR runtime ownership surface must preserve the staged exclusions."),
		Surface.ExplicitExclusions.Contains(TEXT("platform-global guardian ownership"))
			&& Surface.ExplicitExclusions.Contains(TEXT("launch-tier packaged controller certification ownership"))
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistXrPreferenceAndBindingProfileTest,
	"HyperTwist.FirstParty.XR.PreferenceAndBindingProfiles",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistXrPreferenceAndBindingProfileTest::RunTest(const FString& Parameters)
{
	const FHyperTwistXrPreferenceProfile PreferenceProfile =
		UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile();
	TestTrue(
		TEXT("The XR preference profile must be structurally valid."),
		PreferenceProfile.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The XR preference profile id must stay stable."),
		PreferenceProfile.ProfileId,
		FString(TEXT("xr-openxr-training-preferences/v1"))
	);
	TestTrue(
		TEXT("The XR preference profile must preserve the owned preference-field roster."),
		PreferenceProfile.PreferenceFieldTags.Contains(TEXT("movement speed"))
			&& PreferenceProfile.PreferenceFieldTags.Contains(TEXT("snap turn"))
			&& PreferenceProfile.PreferenceFieldTags.Contains(TEXT("world scale"))
	);
	TestTrue(
		TEXT("The XR preference profile must preserve comfort-surface tags."),
		PreferenceProfile.ComfortSurfaceTags.Contains(TEXT("comfort vignette strength"))
			&& PreferenceProfile.ComfortSurfaceTags.Contains(TEXT("seated height offset"))
	);

	const FHyperTwistXrControllerBindingProfile BindingProfile =
		UHyperTwistXrRuntimeLibrary::GetDefaultXrControllerBindingProfile();
	TestTrue(
		TEXT("The XR controller binding profile must be structurally valid."),
		BindingProfile.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The XR controller binding profile id must stay stable."),
		BindingProfile.ProfileId,
		FString(TEXT("xr-openxr-motion-controllers/v1"))
	);
	TestTrue(
		TEXT("The XR controller binding profile must preserve the owned axis roster."),
		BindingProfile.RequiredAxisNames.Contains(TEXT("HyperTwistXr_MoveForward"))
			&& BindingProfile.RequiredAxisNames.Contains(TEXT("HyperTwistXr_Turn"))
			&& BindingProfile.RequiredAxisNames.Contains(TEXT("HyperTwistXr_RightTrigger"))
	);
		TestTrue(
			TEXT("The XR controller binding profile must preserve the supported controller families."),
			BindingProfile.ControllerFamilyIds.Contains(TEXT("controller/oculus-touch"))
				&& BindingProfile.ControllerFamilyIds.Contains(TEXT("controller/valve-index"))
		);

		const FHyperTwistXrControllerSettingsOwnershipSurface ControllerSettingsSurface =
			UHyperTwistXrRuntimeLibrary::BuildBundledXrControllerSettingsOwnershipSurface();
		TestTrue(
			TEXT("The XR controller settings ownership surface must be structurally valid."),
			ControllerSettingsSurface.IsStructurallyValid()
		);
		TestEqual(
			TEXT("The XR controller settings ownership owner id must stay stable."),
			ControllerSettingsSurface.SettingsOwnerId,
			FString(TEXT("xr-openxr-controller-settings-owner/v1"))
		);
		TestEqual(
			TEXT("The XR controller settings rebinding surface id must stay stable."),
			ControllerSettingsSurface.RebindingSurfaceId,
			FString(TEXT("xr-openxr-controller-rebinding-surface/v1"))
		);
		TestEqual(
			TEXT("The XR controller settings default layout preset id must stay stable."),
			ControllerSettingsSurface.DefaultLayoutPresetId,
			FString(TEXT("xr-openxr-controller-layout-default/v1"))
		);
		TestTrue(
			TEXT("The XR controller settings ownership surface must preserve the exact shipped rebindable XR input names."),
			ControllerSettingsSurface.RebindableInputNames.Contains(TEXT("HyperTwistXr_VerticalAdjust"))
				&& ControllerSettingsSurface.RebindableInputNames.Contains(TEXT("HyperTwistXr_Recenter"))
				&& ControllerSettingsSurface.RebindableInputNames.Contains(TEXT("HyperTwistXr_TogglePostureMode"))
		);
		TestTrue(
			TEXT("The XR controller settings ownership surface must keep packaged controller proof explicitly outside the current packet."),
			ControllerSettingsSurface.ExplicitExclusions.Contains(
				TEXT("launch-tier packaged controller certification ownership"))
				&& !ControllerSettingsSurface.bWindowsPackagedControllerProofReady
		);
		return true;
	}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistXrTrainingPawnDefaultsTest,
	"HyperTwist.FirstParty.XR.TrainingPawnDefaults",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistXrTrainingPawnDefaultsTest::RunTest(const FString& Parameters)
{
	const AHyperTwistXrTrainingPawn* PawnDefaults = GetDefault<AHyperTwistXrTrainingPawn>();
	TestNotNull(TEXT("The XR training pawn defaults must exist."), PawnDefaults);
	if (PawnDefaults == nullptr)
	{
		return false;
	}

	TestNotNull(
		TEXT("The XR training pawn must allocate a scene root."),
		PawnDefaults->SceneRoot.Get()
	);
	TestNotNull(
		TEXT("The XR training pawn must allocate a VR origin."),
		PawnDefaults->VROrigin.Get()
	);
	TestNotNull(
		TEXT("The XR training pawn must allocate a camera."),
		PawnDefaults->CameraComponent.Get()
	);
	TestNotNull(
		TEXT("The XR training pawn must allocate a left controller."),
		PawnDefaults->LeftController.Get()
	);
	TestNotNull(
		TEXT("The XR training pawn must allocate a right controller."),
		PawnDefaults->RightController.Get()
	);
	TestTrue(
		TEXT("The XR training pawn must start with a valid owned preference profile and binding profile."),
		PawnDefaults->ActivePreferenceProfile.IsStructurallyValid()
			&& PawnDefaults->ActiveControllerBindingProfile.IsStructurallyValid()
	);
	TestTrue(
		TEXT("The XR training pawn must preserve the bounded runtime ownership line."),
		PawnDefaults->BuildRuntimeStatusLine().Contains(TEXT("xr/openxr-desktop-training-runtime-owner"))
			&& PawnDefaults->BuildRuntimeStatusLine().Contains(TEXT("xr-openxr-training-preferences/v1"))
			&& PawnDefaults->BuildRuntimeStatusLine().Contains(TEXT("xr-openxr-motion-controllers/v1"))
			&& PawnDefaults->BuildRuntimeStatusLine().Contains(TEXT("vertical offset 0"))
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistXrTrainingPawnPreferenceHardeningTest,
	"HyperTwist.FirstParty.XR.TrainingPawnPreferenceHardening",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistXrTrainingPawnPreferenceHardeningTest::RunTest(
	const FString& Parameters
)
{
	AHyperTwistXrTrainingPawn* Pawn =
		NewObject<AHyperTwistXrTrainingPawn>(GetTransientPackage());
	TestNotNull(TEXT("The XR training pawn must be constructible for hardening checks."), Pawn);
	if (Pawn == nullptr)
	{
		return false;
	}

	FHyperTwistXrPreferenceProfile PreferenceProfile =
		UHyperTwistXrRuntimeLibrary::GetDefaultXrPreferenceProfile();
	PreferenceProfile.MoveSpeedUnitsPerSecond = -25.0f;
	PreferenceProfile.VerticalAdjustmentUnitsPerSecond = -5.0f;
	PreferenceProfile.SnapTurnDegrees = 0.0f;
	PreferenceProfile.SnapTurnAxisThreshold = 2.0f;
	PreferenceProfile.SnapTurnResetThreshold = 2.0f;
	PreferenceProfile.SmoothTurnDegreesPerSecond = 12.0f;
	PreferenceProfile.PointerLengthUnits = 0.0f;
	PreferenceProfile.WorldToMetersScale = 0.0f;
	PreferenceProfile.ComfortVignetteStrength = 9.0f;
	PreferenceProfile.ControllerDeadZone = -0.2f;
	PreferenceProfile.PreferenceFieldTags.Reset();
	PreferenceProfile.ComfortSurfaceTags.Reset();
	PreferenceProfile.ExplicitExclusions.Reset();
	PreferenceProfile.PostureMode = EHyperTwistXrPostureMode::Hybrid;

	Pawn->ApplyPreferenceProfile(PreferenceProfile);

	TestTrue(
		TEXT("The XR training pawn must clamp invalid movement and vertical-adjust speeds."),
		Pawn->ActivePreferenceProfile.MoveSpeedUnitsPerSecond > 0.0f
			&& Pawn->ActivePreferenceProfile.VerticalAdjustmentUnitsPerSecond > 0.0f
			&& Pawn->ActivePreferenceProfile.MoveSpeedUnitsPerSecond
				!= PreferenceProfile.MoveSpeedUnitsPerSecond
			&& Pawn->ActivePreferenceProfile.VerticalAdjustmentUnitsPerSecond
				!= PreferenceProfile.VerticalAdjustmentUnitsPerSecond
	);
	TestTrue(
		TEXT("The XR training pawn must clamp turning, pointer, world-scale, comfort, and dead-zone bounds."),
		Pawn->ActivePreferenceProfile.SnapTurnDegrees >= 5.0f
			&& Pawn->ActivePreferenceProfile.SnapTurnAxisThreshold <= 0.95f
			&& Pawn->ActivePreferenceProfile.SnapTurnResetThreshold
				< Pawn->ActivePreferenceProfile.SnapTurnAxisThreshold
			&& Pawn->ActivePreferenceProfile.SmoothTurnDegreesPerSecond >= 45.0f
			&& Pawn->ActivePreferenceProfile.PointerLengthUnits > 0.0f
			&& Pawn->ActivePreferenceProfile.WorldToMetersScale > 0.0f
			&& Pawn->ActivePreferenceProfile.ComfortVignetteStrength <= 1.0f
			&& Pawn->ActivePreferenceProfile.ControllerDeadZone >= 0.0f
	);
	TestTrue(
		TEXT("The XR training pawn must restore default metadata arrays when a custom profile clears them."),
		Pawn->ActivePreferenceProfile.PreferenceFieldTags.Num() > 0
			&& Pawn->ActivePreferenceProfile.ComfortSurfaceTags.Num() > 0
			&& Pawn->ActivePreferenceProfile.ExplicitExclusions.Num() > 0
	);
	TestTrue(
		TEXT("The XR training pawn must apply the sanitized pointer anchor length and hybrid posture offset."),
		Pawn->LeftPointerAnchor != nullptr
			&& Pawn->RightPointerAnchor != nullptr
			&& Pawn->VROrigin != nullptr
			&& FMath::IsNearlyEqual(
				Pawn->LeftPointerAnchor->GetRelativeLocation().X,
				Pawn->ActivePreferenceProfile.PointerLengthUnits
			)
			&& FMath::IsNearlyEqual(
				Pawn->RightPointerAnchor->GetRelativeLocation().X,
				Pawn->ActivePreferenceProfile.PointerLengthUnits
			)
			&& FMath::IsNearlyEqual(
				Pawn->VROrigin->GetRelativeLocation().Z,
				-20.0f
			)
	);
	return true;
}

#endif
