// 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 HyperTwistQbrPhase6RASTestInternal
{
	FHyperTwistTrainingDeck MakeRecognitionDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-as/qbr-font-review");
		Deck.Title = TEXT("Phase 6R-AS Qbr Font Review");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

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

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

	const FHyperTwistVisionFontReviewDescriptor* FindFontReviewDescriptor(
		const TArray<FHyperTwistVisionFontReviewDescriptor>& Reviews,
		const FString& ExpectedDescriptorId
	)
	{
		return Reviews.FindByPredicate(
			[&ExpectedDescriptorId](const FHyperTwistVisionFontReviewDescriptor& Candidate)
			{
				return Candidate.FontReviewDescriptorId == ExpectedDescriptorId;
			}
		);
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6RASSessionConfigTest,
	"HyperTwist.Permissive.Qbr.Phase6R.AS.BundledFontReviewSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	FString OpenError;
	TestTrue(TEXT("The recognition session must open for the font-review 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 webcam shell must expose the bounded font review profile id."),
		SessionState.SessionConfig.ShellProfileDefinition.FontReviewProfileId,
		TEXT("qbr-bundled-font-review-v1")
	);
	TestTrue(
		TEXT("The webcam shell font review profile must stay structurally valid."),
		SessionState.SessionConfig.ShellProfileDefinition.FontReviewProfileDefinition.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The webcam shell must expose five bounded font review descriptors."),
		SessionState.SessionConfig.ShellProfileDefinition.SupportedFontReviews.Num(),
		5
	);

	TestEqual(
		TEXT("The solve explanation profile must expose the bounded font review profile id."),
		SessionState.SessionConfig.SolveExplanationProfileDefinition.FontReviewProfileId,
		TEXT("qbr-bundled-font-review-v1")
	);
	TestTrue(
		TEXT("The solve explanation font review profile must stay structurally valid."),
		SessionState.SessionConfig.SolveExplanationProfileDefinition.FontReviewProfileDefinition.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The solve explanation profile must expose five bounded font review descriptors."),
		SessionState.SessionConfig.SolveExplanationProfileDefinition.SupportedFontReviews.Num(),
		5
	);

	const FHyperTwistVisionLocaleOption* ArabicLocale =
		HyperTwistQbrPhase6RASTestInternal::FindLocaleOption(
			SessionState.SessionConfig.SolveExplanationProfileDefinition.SupportedLocales,
			TEXT("ar")
		);
	TestNotNull(TEXT("The Arabic locale option must be present."), ArabicLocale);
	if (ArabicLocale != nullptr)
	{
		TestEqual(
			TEXT("Arabic must map to the bounded Arabic font review descriptor."),
			ArabicLocale->FontReviewDescriptorId,
			TEXT("font-review/qbr/arabic-core")
		);
	}

	const FHyperTwistVisionLocaleOption* SimplifiedChineseLocale =
		HyperTwistQbrPhase6RASTestInternal::FindLocaleOption(
			SessionState.SessionConfig.SolveExplanationProfileDefinition.SupportedLocales,
			TEXT("zh")
		);
	TestNotNull(TEXT("The Simplified Chinese locale option must be present."), SimplifiedChineseLocale);
	if (SimplifiedChineseLocale != nullptr)
	{
		TestEqual(
			TEXT("Simplified Chinese must map to the bounded CJK simplified font review descriptor."),
			SimplifiedChineseLocale->FontReviewDescriptorId,
			TEXT("font-review/qbr/cjk-simplified")
		);
	}

	const FHyperTwistVisionFontReviewDescriptor* ArabicFontReview =
		HyperTwistQbrPhase6RASTestInternal::FindFontReviewDescriptor(
			SessionState.SessionConfig.SolveExplanationProfileDefinition.SupportedFontReviews,
			TEXT("font-review/qbr/arabic-core")
		);
	TestNotNull(TEXT("The Arabic font review descriptor must be present."), ArabicFontReview);
	if (ArabicFontReview != nullptr)
	{
		TestEqual(
			TEXT("The Arabic descriptor must preserve the complex shaping review profile."),
			ArabicFontReview->ScriptCoverageProfileId,
			TEXT("rtl-arabic-complex-shaping")
		);
		TestFalse(
			TEXT("The Arabic descriptor must not imply bundled font redistribution."),
			ArabicFontReview->bBundledWithCodeDistribution
		);
		TestTrue(
			TEXT("The Arabic descriptor must keep standalone donor rendering deferred."),
			ArabicFontReview->bStandaloneDonorRenderingDeferred
		);
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistQbrPhase6RASActiveStateTest,
	"HyperTwist.Permissive.Qbr.Phase6R.AS.BundledFontReviewActiveState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistQbrPhase6RASActiveStateTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistQbrPhase6RASTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-as-state-session"));
	TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-AS."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

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

	TestTrue(TEXT("The webcam shell state must stay structurally valid."), Result.ShellState.IsStructurallyValid());
	TestEqual(TEXT("The default shell locale must remain English."), Result.ShellState.LocaleCode, TEXT("en"));
	TestEqual(
		TEXT("The shell state must preserve the bounded font review profile id."),
		Result.ShellState.FontReviewProfileId,
		TEXT("qbr-bundled-font-review-v1")
	);
	TestEqual(
		TEXT("The default shell locale must map to the bounded Latin font review descriptor."),
		Result.ShellState.ActiveFontReviewDescriptorId,
		TEXT("font-review/qbr/latin-core")
	);
	TestTrue(TEXT("The shell must keep bundled donor font shipping deferred."), Result.ShellState.bBundledFontAssetDeferred);

	TestTrue(TEXT("The solve-guidance state must stay structurally valid."), Result.SolveExplanationState.IsStructurallyValid());
	TestEqual(TEXT("The default solve locale must remain English."), Result.SolveExplanationState.ActiveLocaleCode, TEXT("en"));
	TestEqual(
		TEXT("The solve state must preserve the bounded font review profile id."),
		Result.SolveExplanationState.FontReviewProfileId,
		TEXT("qbr-bundled-font-review-v1")
	);
	TestEqual(
		TEXT("The default solve locale must map to the bounded Latin font review descriptor."),
		Result.SolveExplanationState.ActiveFontReviewDescriptorId,
		TEXT("font-review/qbr/latin-core")
	);
	TestTrue(TEXT("The solve guidance state must keep bundled donor font shipping deferred."), Result.SolveExplanationState.bBundledFontAssetDeferred);

	return true;
}

#endif
