// 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 HyperTwistClassicCubePhase6RRTestInternal
{
	FHyperTwistTrainingDeck MakeRecognitionDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-r/classic-cube-correction-shell");
		Deck.Title = TEXT("Phase 6R-R Classic Cube Correction Shell");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

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

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubePhase6RRCorrectionSessionConfigTest,
	"HyperTwist.Permissive.ClassicCube.Phase6R.R.CorrectionSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	FString OpenError;
	TestTrue(TEXT("The recognition session must open for the correction 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 correction shell route must normalize the first-party correction profile id."),
		SessionState.SessionConfig.CorrectionProfileId,
		TEXT("classic-cube-correction-shell-v1")
	);
	TestTrue(
		TEXT("The correction shell route must provide a structurally valid correction profile definition."),
		SessionState.SessionConfig.CorrectionProfileDefinition.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The correction shell route must retain four bounded correction actions."),
		SessionState.SessionConfig.CorrectionProfileDefinition.ActionBindings.Num(),
		4
	);
	TestTrue(
		TEXT("The correction shell route must support targeted re-scan in this packet."),
		SessionState.SessionConfig.CorrectionProfileDefinition.bSupportsTargetedRescan
	);
	TestTrue(
		TEXT("The correction shell route must support manual cube edit in this packet."),
		SessionState.SessionConfig.CorrectionProfileDefinition.bSupportsManualCubeEdit
	);
	TestTrue(
		TEXT("The correction shell route must preserve explicit stage-restart support."),
		SessionState.SessionConfig.CorrectionProfileDefinition.bSupportsStageRestart
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubePhase6RRCorrectionPartialStateTest,
	"HyperTwist.Permissive.ClassicCube.Phase6R.R.CorrectionPartialState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

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

	TestTrue(TEXT("The partial correction route must carry a structurally valid correction state."), Result.CorrectionState.IsStructurallyValid());
	TestTrue(TEXT("The partial correction route must keep correction open before capture completes."), Result.CorrectionState.bCorrectionRequired);
	TestFalse(TEXT("The partial correction route must not report any contradictions after a clean first face."), Result.CorrectionState.bManualReviewRequired);
	TestEqual(TEXT("The partial correction route must target the first missing canonical face next."), Result.CorrectionState.ActiveTargetFaceId, TEXT("U"));
	TestEqual(TEXT("The partial correction route must tag the active target as a missing-face correction."), Result.CorrectionState.ActiveTarget.ReasonId, TEXT("missing-face"));
	TestEqual(TEXT("The partial correction route must route the next action back to capture."), Result.CorrectionState.NextRecommendedActionId, TEXT("capture-target-face"));
	TestEqual(TEXT("The partial correction route must keep the correction stage in video-stream."), Result.CorrectionState.RecommendedStageId, TEXT("video-stream"));
	TestEqual(TEXT("The partial correction route must report five faces still missing."), Result.CorrectionState.MissingFaceCount, 5);
	TestEqual(TEXT("The partial correction route must report zero contradiction objects after the first clean face."), Result.CorrectionState.ContradictionCount, 0);
	TestTrue(TEXT("The partial correction route must keep targeted re-scan or capture visible."), Result.CorrectionState.bTargetedRescanVisible);
	TestFalse(TEXT("Solve guidance must remain blocked while correction stays open."), Result.SolveExplanationState.bReadyForExplanation);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubePhase6RRCorrectionRevisionStateTest,
	"HyperTwist.Permissive.ClassicCube.Phase6R.R.CorrectionRevisionState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistClassicCubePhase6RRCorrectionRevisionStateTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistClassicCubePhase6RRTestInternal::MakeRecognitionSubsystem(TEXT("phase6r-r-revision-session"));
	TestNotNull(TEXT("The recognition subsystem must be constructed for Phase 6R-R."), 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 = HyperTwistClassicCubePhase6RRTestInternal::CommitFace(
			TrainingSubsystem,
			FaceOrder[FaceOrdinal],
			FaceOrdinal + 1
		);
	}
	Result = HyperTwistClassicCubePhase6RRTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 7);

	const FHyperTwistTrainingRecognitionSessionState SessionState =
		TrainingSubsystem->GetActiveRecognitionSessionState();
	TestTrue(TEXT("The revision route must keep a structurally valid reconstruction session."), Result.ReconstructionSession.IsStructurallyValid());
	TestTrue(TEXT("The revision route must keep the classic cube net complete after a face recommit."), Result.ReconstructionSession.bHasCompleteClassicCubeNet);
	TestEqual(TEXT("The revision route must record exactly one bounded reconstruction revision."), Result.ReconstructionSession.TotalRevisionCount, 1);
	TestTrue(TEXT("The revision route must carry a structurally valid correction state."), Result.CorrectionState.IsStructurallyValid());
	TestTrue(TEXT("The revision route must reopen correction after a face recommit."), Result.CorrectionState.bCorrectionRequired);
	TestTrue(TEXT("The revision route must require manual review after a full-net recommit."), Result.CorrectionState.bManualReviewRequired);
	TestTrue(TEXT("The revision route must expose manual cube edit once review is required."), Result.CorrectionState.bManualCubeEditVisible);
	TestFalse(TEXT("The revision route must not report correction complete while review is still open."), Result.CorrectionState.bCorrectionComplete);
	TestEqual(TEXT("The revision route must keep the recommitted face as the active correction target."), Result.CorrectionState.ActiveTargetFaceId, TEXT("F"));
	TestEqual(TEXT("The revision route must classify the active correction as a revised-face target."), Result.CorrectionState.ActiveTarget.ReasonId, TEXT("revised-face"));
	TestEqual(TEXT("The revision route must hand the next action to manual correction once the full net exists."), Result.CorrectionState.NextRecommendedActionId, TEXT("apply-manual-corrections"));
	TestEqual(TEXT("The revision route must switch the correction stage into cube-edit."), Result.CorrectionState.RecommendedStageId, TEXT("cube-edit"));
	TestEqual(TEXT("The revision route must surface one contradiction object for the recommit."), Result.CorrectionState.ContradictionCount, 1);
	TestEqual(TEXT("The revision route must tag the contradiction as a manual revision."), Result.CorrectionState.Contradictions[0].ContradictionKind, TEXT("manual-revision"));
	TestFalse(TEXT("Solve guidance must stay blocked until the correction review closes."), Result.SolveExplanationState.bReadyForExplanation);
	TestFalse(TEXT("The solve explanation start action must stay hidden while correction is open."), Result.SolveExplanationState.bStartExplanationVisible);
	TestEqual(TEXT("The solve explanation route must redirect back to cube-edit while correction is open."), Result.SolveExplanationState.RecommendedStageId, TEXT("cube-edit"));
	TestEqual(TEXT("A bounded face recommit must still count as one correction event."), SessionState.CorrectionEventCount, 1);
	return true;
}

#endif
