// 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 HyperTwistQbrPhase6RAKTestInternal
{
	FHyperTwistTrainingDeck MakeRecognitionDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-ak/qbr-locale-guidance-routing");
		Deck.Title = TEXT("Phase 6R-AK Qbr Locale Guidance Routing");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

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

	UHyperTwistTrainingSubsystem* MakeRecognitionSubsystem(const FString& SessionId)
	{
		UGameInstance* GameInstance = NewObject<UGameInstance>();
		if (GameInstance == nullptr)
		{
			return nullptr;
		}

		UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
		if (TrainingSubsystem == nullptr)
		{
			return nullptr;
		}

		ForceMockRecognitionClient(TrainingSubsystem);
		const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
			MakeRecognitionDeck(),
			TEXT("phase6r-ak-user"),
			SessionId,
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted
		);
		return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr;
	}

	void SubmitPreviewFrame(
		UHyperTwistTrainingSubsystem* TrainingSubsystem,
		const FString& FaceId,
		const int32 FrameOrdinal
	)
	{
		FHyperTwistVisionFrameEnvelope Frame;
		Frame.FrameOrdinal = FrameOrdinal;
		Frame.TargetFaceHint = FaceId;
		TrainingSubsystem->SubmitActiveRecognitionFrame(Frame);
	}

	FHyperTwistVisionCommitResult CommitFace(
		UHyperTwistTrainingSubsystem* TrainingSubsystem,
		const FString& FaceId,
		const int32 FrameOrdinal
	)
	{
		SubmitPreviewFrame(TrainingSubsystem, FaceId, FrameOrdinal);

		FHyperTwistVisionCommitRequest Request;
		Request.TargetFace = FaceId;
		return TrainingSubsystem->CommitActiveRecognitionObservation(Request);
	}

	bool HasLocaleCode(const TArray<FString>& LocaleCodes, const FString& ExpectedLocaleCode)
	{
		return LocaleCodes.Contains(ExpectedLocaleCode);
	}

	const FHyperTwistVisionLocaleOption* FindLocaleOption(
		const TArray<FHyperTwistVisionLocaleOption>& LocaleOptions,
		const FString& ExpectedLocaleCode
	)
	{
		return LocaleOptions.FindByPredicate(
			[&ExpectedLocaleCode](const FHyperTwistVisionLocaleOption& Candidate)
			{
				return Candidate.LocaleCode == ExpectedLocaleCode;
			}
		);
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6RAKSessionConfigTest,
	"HyperTwist.Permissive.Qbr.Phase6R.AK.LocaleGuidanceSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6RAKSessionConfigTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistQbrPhase6RAKTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-ak-config-session"));
	TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-AK."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	FString OpenError;
	TestTrue(TEXT("The recognition session must open for the locale-guidance 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 must expose the ten source-backed locale entries."),
		SessionState.SessionConfig.ShellProfileDefinition.SupportedLocales.Num(),
		10
	);
	TestEqual(
		TEXT("The webcam shell must default to English."),
		SessionState.SessionConfig.ShellProfileDefinition.DefaultLocaleCode,
		TEXT("en")
	);
	TestEqual(
		TEXT("The solve-explanation profile must expose the same ten source-backed locale entries."),
		SessionState.SessionConfig.SolveExplanationProfileDefinition.SupportedLocales.Num(),
		10
	);
	TestEqual(
		TEXT("The solve-explanation locale overlay must keep the bounded routing mode id."),
		SessionState.SessionConfig.SolveExplanationProfileDefinition.LocaleGuidanceMode,
		TEXT("qbr-locale-guidance-routing-v1")
	);
	TestEqual(
		TEXT("The solve-explanation profile must preserve the qbr starting-orientation reminder."),
		SessionState.SessionConfig.SolveExplanationProfileDefinition.StartingOrientationHint,
		TEXT("front: green | top: white")
	);

	const FHyperTwistVisionLocaleOption* EnglishLocale =
		HyperTwistQbrPhase6RAKTestInternal::FindLocaleOption(
			SessionState.SessionConfig.SolveExplanationProfileDefinition.SupportedLocales,
			TEXT("en")
		);
	TestNotNull(TEXT("The English locale option must be present."), EnglishLocale);
	if (EnglishLocale != nullptr)
	{
		TestEqual(TEXT("The English locale label must remain source-backed."), EnglishLocale->DisplayLabel, TEXT("English"));
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6RAKPartialStateTest,
	"HyperTwist.Permissive.Qbr.Phase6R.AK.LocaleGuidancePartialState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6RAKPartialStateTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistQbrPhase6RAKTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-ak-partial-session"));
	TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-AK."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	const FHyperTwistVisionCommitResult Result =
		HyperTwistQbrPhase6RAKTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 1);

	TestTrue(TEXT("The qbr webcam shell state must stay structurally valid."), Result.ShellState.IsStructurallyValid());
	TestEqual(TEXT("The shell locale must default to English."), Result.ShellState.LocaleCode, TEXT("en"));
	TestEqual(TEXT("The shell locale label must remain English by default."), Result.ShellState.LocaleDisplayLabel, TEXT("English"));
	TestEqual(TEXT("The shell must surface ten supported locale codes."), Result.ShellState.AvailableLocaleCodes.Num(), 10);
	TestTrue(TEXT("The shell must expose Spanish as a source-backed locale option."), HyperTwistQbrPhase6RAKTestInternal::HasLocaleCode(Result.ShellState.AvailableLocaleCodes, TEXT("es")));
	TestTrue(TEXT("The shell must expose Farsi as a source-backed locale option."), HyperTwistQbrPhase6RAKTestInternal::HasLocaleCode(Result.ShellState.AvailableLocaleCodes, TEXT("fa")));

	TestTrue(TEXT("The partial solve route must carry a structurally valid solve explanation state."), Result.SolveExplanationState.IsStructurallyValid());
	TestEqual(TEXT("The solve-guidance locale must default to English."), Result.SolveExplanationState.ActiveLocaleCode, TEXT("en"));
	TestEqual(TEXT("The solve-guidance locale label must remain English by default."), Result.SolveExplanationState.ActiveLocaleDisplayLabel, TEXT("English"));
	TestEqual(TEXT("The bounded locale-guidance mode id must be present."), Result.SolveExplanationState.LocaleGuidanceMode, TEXT("qbr-locale-guidance-routing-v1"));
	TestEqual(TEXT("The qbr starting-orientation reminder must stay visible in the state."), Result.SolveExplanationState.StartingOrientationHint, TEXT("front: green | top: white"));
	TestTrue(TEXT("Locale cycling must stay visible in the bounded guidance overlay."), Result.SolveExplanationState.bLocaleCycleVisible);
	TestTrue(TEXT("The starting-orientation reminder must stay visible."), Result.SolveExplanationState.bStartingOrientationHintVisible);
	TestEqual(TEXT("The partial locale-guidance route must still direct the user back to capture."), Result.SolveExplanationState.RecommendedStageId, TEXT("capture-remaining-faces"));
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6RAKCompleteStateTest,
	"HyperTwist.Permissive.Qbr.Phase6R.AK.LocaleGuidanceCompleteState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6RAKCompleteStateTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistQbrPhase6RAKTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-ak-complete-session"));
	TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-AK."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	const TArray<FString> FaceOrder = {
		TEXT("U"),
		TEXT("R"),
		TEXT("F"),
		TEXT("D"),
		TEXT("L"),
		TEXT("B")
	};

	FHyperTwistVisionCommitResult Result;
	for (int32 FaceOrdinal = 0; FaceOrdinal < FaceOrder.Num(); ++FaceOrdinal)
	{
		Result = HyperTwistQbrPhase6RAKTestInternal::CommitFace(
			TrainingSubsystem,
			FaceOrder[FaceOrdinal],
			FaceOrdinal + 1
		);
	}

	TestTrue(TEXT("The completed locale-guidance route must carry a structurally valid solve explanation state."), Result.SolveExplanationState.IsStructurallyValid());
	TestTrue(TEXT("Solve guidance must unlock after the full reconstruction completes."), Result.SolveExplanationState.bReadyForExplanation);
	TestEqual(TEXT("The completed locale-guidance route must recommend the orientation stage first."), Result.SolveExplanationState.RecommendedStageId, TEXT("orient-centers"));
	TestEqual(TEXT("The completed locale-guidance route must preserve the active locale code."), Result.SolveExplanationState.ActiveLocaleCode, TEXT("en"));
	TestEqual(TEXT("The completed locale-guidance route must preserve the active locale label."), Result.SolveExplanationState.ActiveLocaleDisplayLabel, TEXT("English"));
	TestEqual(TEXT("The completed locale-guidance route must preserve the qbr starting-orientation reminder."), Result.SolveExplanationState.StartingOrientationHint, TEXT("front: green | top: white"));
	TestTrue(TEXT("Locale cycling must remain visible after reconstruction completes."), Result.SolveExplanationState.bLocaleCycleVisible);
	TestTrue(TEXT("The starting-orientation reminder must remain visible after reconstruction completes."), Result.SolveExplanationState.bStartingOrientationHintVisible);
	return true;
}

#endif
