// 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 HyperTwistQbrPhase6RBTestInternal
{
	FHyperTwistTrainingDeck MakeRecognitionDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-b/qbr-recognition");
		Deck.Title = TEXT("Phase 6R-B Qbr Recognition");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("phase6r-b-case");
		TrainingCase.PuzzleId = TEXT("cube/3x3x3");
		TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Recognition;
		TrainingCase.PromptLabel = TEXT("Phase 6R-B 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"));
		}
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6RBCalibrationSessionConfigTest,
	"HyperTwist.Permissive.Qbr.Phase6R.B.CalibrationSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6RBCalibrationSessionConfigTest::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;
	}
	HyperTwistQbrPhase6RBTestInternal::ForceMockRecognitionClient(TrainingSubsystem);

	const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
		HyperTwistQbrPhase6RBTestInternal::MakeRecognitionDeck(),
		TEXT("phase6r-b-user"),
		TEXT("phase6r-b-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 a classic 3x3 run."),
		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-bounded route must default to the classic-cube calibration profile id."),
		SessionState.SessionConfig.CalibrationProfile,
		TEXT("classic-cube-default")
	);
	TestTrue(
		TEXT("The qbr-bounded route must provide a structurally valid calibration profile definition."),
		SessionState.SessionConfig.CalibrationProfileDefinition.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The calibration profile must preserve six retained swatches."),
		SessionState.SessionConfig.CalibrationProfileDefinition.PaletteSwatches.Num(),
		6
	);
	if (SessionState.SessionConfig.CalibrationProfileDefinition.PaletteSwatches.Num() > 0)
	{
		TestEqual(
			TEXT("The front swatch must preserve the green/F calibration mapping."),
			SessionState.SessionConfig.CalibrationProfileDefinition.PaletteSwatches[0].Notation,
			TEXT("F")
		);
	}
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6RBPreviewObservationTest,
	"HyperTwist.Permissive.Qbr.Phase6R.B.PreviewObservation",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6RBPreviewObservationTest::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;
	}
	HyperTwistQbrPhase6RBTestInternal::ForceMockRecognitionClient(TrainingSubsystem);

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

	FHyperTwistVisionPreviewResult Result;
	for (int32 FrameOrdinal = 1; FrameOrdinal <= 3; ++FrameOrdinal)
	{
		FHyperTwistVisionFrameEnvelope Frame;
		Frame.FrameOrdinal = FrameOrdinal;
		Frame.TargetFaceHint = TEXT("F");
		Result = TrainingSubsystem->SubmitActiveRecognitionFrame(Frame);
	}

	TestEqual(TEXT("The third preview should move the mock into commit-ready guidance."), Result.Guidance, TEXT("commit-ready"));
	TestTrue(
		TEXT("The preview must expose a structurally valid ordered face observation."),
		Result.ObservedFace.IsStructurallyValid()
	);
	TestEqual(TEXT("The ordered face observation must resolve the target face."), Result.ObservedFace.FaceId, TEXT("F"));
	TestEqual(TEXT("The ordered face observation must contain nine stickers."), Result.ObservedFace.OrderedStickers.Num(), 9);
	TestTrue(TEXT("The preview observation must report commit readiness."), Result.ObservedFace.bCommitReady);
	TestEqual(
		TEXT("The preview observation must preserve row-major ordering."),
		Result.ObservedFace.OrderingProfile,
		TEXT("3x3-row-major")
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6RBCommitObservationTest,
	"HyperTwist.Permissive.Qbr.Phase6R.B.CommitObservation",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6RBCommitObservationTest::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;
	}
	HyperTwistQbrPhase6RBTestInternal::ForceMockRecognitionClient(TrainingSubsystem);

	const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
		HyperTwistQbrPhase6RBTestInternal::MakeRecognitionDeck(),
		TEXT("phase6r-b-user"),
		TEXT("phase6r-b-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);

	TestEqual(TEXT("The commit result must keep the normalized face commit kind."), Result.CommitKind, TEXT("face"));
	TestEqual(TEXT("The commit result must report the committed face."), Result.CommittedUnit, TEXT("F"));
	TestTrue(
		TEXT("The commit result must expose a structurally valid ordered face observation."),
		Result.ObservedFace.IsStructurallyValid()
	);
	TestTrue(TEXT("The commit face observation must be commit-ready."), Result.ObservedFace.bCommitReady);
	TestTrue(TEXT("The mock commit path should not report warnings for an open session."), Result.Warnings.IsEmpty());
	return true;
}

#endif
