// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "Engine/GameInstance.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
#include "UObject/UnrealType.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistQbrPhase6ROTestInternal
{
	FHyperTwistTrainingDeck MakeRecognitionDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-o/qbr-webcam-shell");
		Deck.Title = TEXT("Phase 6R-O Qbr Webcam Shell");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("phase6r-o-case");
		TrainingCase.PuzzleId = TEXT("cube/3x3x3");
		TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Recognition;
		TrainingCase.PromptLabel = TEXT("Phase 6R-O Recognition");
		TrainingCase.AllowedDeliveryModes = {
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};
		Deck.Cases = {TrainingCase};
		return Deck;
	}

	void ForceMockRecognitionClient(UHyperTwistTrainingSubsystem* TrainingSubsystem)
	{
		if (TrainingSubsystem == nullptr)
		{
			return;
		}

		if (FStrProperty* RecognitionClientKindProperty = FindFProperty<FStrProperty>(
			UHyperTwistTrainingSubsystem::StaticClass(),
			TEXT("RecognitionClientKind")
		))
		{
			RecognitionClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, TEXT("mock"));
		}
	}

	const FHyperTwistVisionShellPanelLayout* FindPanel(
		const FHyperTwistVisionShellProfile& Profile,
		const FString& PanelId
	)
	{
		return Profile.Panels.FindByPredicate(
			[&PanelId](const FHyperTwistVisionShellPanelLayout& Candidate)
			{
				return Candidate.PanelId == PanelId;
			}
		);
	}

	const FHyperTwistVisionShellActionBinding* FindAction(
		const FHyperTwistVisionShellProfile& Profile,
		const FString& ActionId
	)
	{
		return Profile.ActionBindings.FindByPredicate(
			[&ActionId](const FHyperTwistVisionShellActionBinding& Candidate)
			{
				return Candidate.ActionId == ActionId;
			}
		);
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6ROWebcamShellSessionConfigTest,
	"HyperTwist.Permissive.Qbr.Phase6R.O.WebcamShellSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6ROWebcamShellSessionConfigTest::RunTest(const FString& Parameters)
{
	UGameInstance* GameInstance = NewObject<UGameInstance>();
	TestNotNull(TEXT("Game instance outer must exist for subsystem construction."), GameInstance);
	if (GameInstance == nullptr)
	{
		return false;
	}

	UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
	TestNotNull(TEXT("Training subsystem must be constructed."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}
	HyperTwistQbrPhase6ROTestInternal::ForceMockRecognitionClient(TrainingSubsystem);

	const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
		HyperTwistQbrPhase6ROTestInternal::MakeRecognitionDeck(),
		TEXT("phase6r-o-user"),
		TEXT("phase6r-o-session"),
		EHyperTwistTrainingDeliveryMode::RecognitionAssisted
	);
	TestTrue(TEXT("The recognition-assisted run must start successfully."), RunState.IsStructurallyValid());
	if (!RunState.IsStructurallyValid())
	{
		return false;
	}

	FString OpenError;
	TestTrue(TEXT("The recognition session must open for the qbr shell route."), TrainingSubsystem->OpenActiveRecognitionSession(OpenError));
	TestTrue(TEXT("Opening the recognition session must not report an error."), OpenError.IsEmpty());

	const FHyperTwistTrainingRecognitionSessionState SessionState =
		TrainingSubsystem->GetActiveRecognitionSessionState();
	TestEqual(
		TEXT("The qbr webcam shell route must normalize the first-party shell profile id."),
		SessionState.SessionConfig.ShellProfileId,
		TEXT("classic-cube-webcam-shell-v1")
	);
	TestTrue(
		TEXT("The qbr webcam shell route must provide a structurally valid shell profile definition."),
		SessionState.SessionConfig.ShellProfileDefinition.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The shell profile must retain six bounded panels."),
		SessionState.SessionConfig.ShellProfileDefinition.Panels.Num(),
		6
	);
	TestEqual(
		TEXT("The shell profile must retain four bounded action bindings."),
		SessionState.SessionConfig.ShellProfileDefinition.ActionBindings.Num(),
		4
	);

	const FHyperTwistVisionShellPanelLayout* PreviewPanel =
		HyperTwistQbrPhase6ROTestInternal::FindPanel(
			SessionState.SessionConfig.ShellProfileDefinition,
			TEXT("preview-sticker-grid")
		);
	TestNotNull(TEXT("The preview sticker grid panel must be present."), PreviewPanel);
	if (PreviewPanel != nullptr)
	{
		TestEqual(TEXT("The preview sticker grid must stay anchored top-left."), PreviewPanel->AnchorId, TEXT("top-left"));
		TestEqual(TEXT("The preview sticker grid must retain a 3x3 shape."), PreviewPanel->GridRows, 3);
	}

	const FHyperTwistVisionShellPanelLayout* CubeNetPanel =
		HyperTwistQbrPhase6ROTestInternal::FindPanel(
			SessionState.SessionConfig.ShellProfileDefinition,
			TEXT("classic-cube-net")
		);
	TestNotNull(TEXT("The classic cube net panel must be present."), CubeNetPanel);
	if (CubeNetPanel != nullptr)
	{
		TestEqual(TEXT("The cube net must stay anchored bottom-right."), CubeNetPanel->AnchorId, TEXT("bottom-right"));
		TestEqual(TEXT("The cube net must retain the bounded 3x4 layout."), CubeNetPanel->GridColumns, 4);
	}

	const FHyperTwistVisionShellActionBinding* SnapshotAction =
		HyperTwistQbrPhase6ROTestInternal::FindAction(
			SessionState.SessionConfig.ShellProfileDefinition,
			TEXT("capture-snapshot")
		);
	TestNotNull(TEXT("The snapshot action binding must be present."), SnapshotAction);
	if (SnapshotAction != nullptr)
	{
		TestEqual(TEXT("The snapshot action must keep the Space binding."), SnapshotAction->InputBinding, TEXT("Space"));
	}

	const FHyperTwistVisionShellActionBinding* LocaleAction =
		HyperTwistQbrPhase6ROTestInternal::FindAction(
			SessionState.SessionConfig.ShellProfileDefinition,
			TEXT("cycle-locale")
		);
	TestNotNull(TEXT("The locale-cycle action binding must be present."), LocaleAction);
	if (LocaleAction != nullptr)
	{
		TestEqual(TEXT("The locale-cycle action must keep the L binding."), LocaleAction->InputBinding, TEXT("L"));
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6ROWebcamShellPreviewStateTest,
	"HyperTwist.Permissive.Qbr.Phase6R.O.WebcamShellPreviewState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6ROWebcamShellPreviewStateTest::RunTest(const FString& Parameters)
{
	UGameInstance* GameInstance = NewObject<UGameInstance>();
	TestNotNull(TEXT("Game instance outer must exist for subsystem construction."), GameInstance);
	if (GameInstance == nullptr)
	{
		return false;
	}

	UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
	TestNotNull(TEXT("Training subsystem must be constructed."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}
	HyperTwistQbrPhase6ROTestInternal::ForceMockRecognitionClient(TrainingSubsystem);

	const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
		HyperTwistQbrPhase6ROTestInternal::MakeRecognitionDeck(),
		TEXT("phase6r-o-user"),
		TEXT("phase6r-o-preview-session"),
		EHyperTwistTrainingDeliveryMode::RecognitionAssisted
	);
	TestTrue(TEXT("The recognition-assisted run must start successfully."), RunState.IsStructurallyValid());
	if (!RunState.IsStructurallyValid())
	{
		return false;
	}

	FHyperTwistVisionFrameEnvelope Frame;
	Frame.TargetFaceHint = TEXT("F");
	const FHyperTwistVisionPreviewResult Result = TrainingSubsystem->SubmitActiveRecognitionFrame(Frame);

	TestTrue(TEXT("The qbr preview shell state must be structurally valid."), Result.ShellState.IsStructurallyValid());
	TestEqual(TEXT("The preview shell must preserve the first-party shell profile id."), Result.ShellState.ShellProfileId, TEXT("classic-cube-webcam-shell-v1"));
	TestEqual(TEXT("The preview shell must start with no scanned sides."), Result.ShellState.ScannedSideCount, 0);
	TestEqual(TEXT("The preview shell must keep the six-side target."), Result.ShellState.TargetSideCount, 6);
	TestEqual(TEXT("The preview shell must preserve the current face hint."), Result.ShellState.TargetFaceHint, TEXT("F"));
	TestEqual(TEXT("The preview shell must preserve the classic default calibration profile id."), Result.ShellState.CalibrationProfileId, TEXT("classic-cube-default"));
	TestEqual(TEXT("The preview shell must preserve the default locale code."), Result.ShellState.LocaleCode, TEXT("en"));
	TestTrue(TEXT("The preview panel must remain visible."), Result.ShellState.bPreviewPanelVisible);
	TestTrue(TEXT("The snapshot panel must remain visible."), Result.ShellState.bSnapshotPanelVisible);
	TestTrue(TEXT("The cube net panel must remain visible."), Result.ShellState.bCubeNetVisible);
	TestTrue(TEXT("The locale indicator must remain visible."), Result.ShellState.bLocaleIndicatorVisible);
	TestFalse(TEXT("The calibration rail should stay hidden outside explicit calibration mode."), Result.ShellState.bCalibrationRailVisible);
	TestEqual(TEXT("The preview shell should not yet report captured faces."), Result.ShellState.CapturedFaceIds.Num(), 0);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6ROWebcamShellCommitStateTest,
	"HyperTwist.Permissive.Qbr.Phase6R.O.WebcamShellCommitState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6ROWebcamShellCommitStateTest::RunTest(const FString& Parameters)
{
	UGameInstance* GameInstance = NewObject<UGameInstance>();
	TestNotNull(TEXT("Game instance outer must exist for subsystem construction."), GameInstance);
	if (GameInstance == nullptr)
	{
		return false;
	}

	UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
	TestNotNull(TEXT("Training subsystem must be constructed."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}
	HyperTwistQbrPhase6ROTestInternal::ForceMockRecognitionClient(TrainingSubsystem);

	const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
		HyperTwistQbrPhase6ROTestInternal::MakeRecognitionDeck(),
		TEXT("phase6r-o-user"),
		TEXT("phase6r-o-commit-session"),
		EHyperTwistTrainingDeliveryMode::RecognitionAssisted
	);
	TestTrue(TEXT("The recognition-assisted run must start successfully."), RunState.IsStructurallyValid());
	if (!RunState.IsStructurallyValid())
	{
		return false;
	}

	FHyperTwistVisionFrameEnvelope Frame;
	Frame.TargetFaceHint = TEXT("F");
	TrainingSubsystem->SubmitActiveRecognitionFrame(Frame);

	FHyperTwistVisionCommitRequest Request;
	Request.TargetFace = TEXT("F");
	const FHyperTwistVisionCommitResult Result =
		TrainingSubsystem->CommitActiveRecognitionObservation(Request);

	TestTrue(TEXT("The qbr commit shell state must be structurally valid."), Result.ShellState.IsStructurallyValid());
	TestEqual(TEXT("The committed shell must report one scanned side."), Result.ShellState.ScannedSideCount, 1);
	TestEqual(TEXT("The committed shell must preserve the six-side target."), Result.ShellState.TargetSideCount, 6);
	TestEqual(TEXT("The committed shell must preserve the last committed face."), Result.ShellState.LastCommittedFaceId, TEXT("F"));
	TestTrue(TEXT("The committed shell must retain the scanned-side counter surface."), Result.ShellState.bScannedSideCounterVisible);
	TestTrue(TEXT("The committed shell must retain the cube-net surface."), Result.ShellState.bCubeNetVisible);
	TestTrue(TEXT("The committed shell must track the committed face id."), Result.ShellState.CapturedFaceIds.Contains(TEXT("F")));
	TestEqual(TEXT("The committed shell must retain the classic default calibration profile id."), Result.ShellState.CalibrationProfileId, TEXT("classic-cube-default"));
	return true;
}

#endif
