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

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

	FHyperTwistVisionCommitResult ResolveCorrection(
		UHyperTwistTrainingSubsystem* TrainingSubsystem,
		const FString& FaceId
	)
	{
		FHyperTwistVisionCommitRequest Request;
		Request.CommitKind = TEXT("correction-resolution");
		Request.TargetFace = FaceId;
		Request.TargetStage = TEXT("cube-edit");
		Request.RequestedActionId = TEXT("apply-manual-corrections");
		Request.ResolutionKind = TEXT("manual-review-accepted");
		return TrainingSubsystem->CommitActiveRecognitionObservation(Request);
	}

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

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

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubePhase6RSCorrectionResolutionUnlockTest,
	"HyperTwist.Permissive.ClassicCube.Phase6R.S.CorrectionResolutionUnlock",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	HyperTwistClassicCubePhase6RSTestInternal::CommitClassicCubeNet(TrainingSubsystem);
	HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 7);
	const FHyperTwistVisionCommitResult Result =
		HyperTwistClassicCubePhase6RSTestInternal::ResolveCorrection(TrainingSubsystem, TEXT("F"));

	const FHyperTwistTrainingRecognitionSessionState SessionState =
		TrainingSubsystem->GetActiveRecognitionSessionState();
	TestTrue(TEXT("The resolution route must surface a structurally valid applied resolution."), Result.AppliedCorrectionResolution.IsStructurallyValid());
	TestEqual(TEXT("The resolution route must apply the reviewed face explicitly."), Result.AppliedCorrectionResolution.FaceId, TEXT("F"));
	TestTrue(TEXT("The resolution route must carry a structurally valid correction state."), Result.CorrectionState.IsStructurallyValid());
	TestFalse(TEXT("The resolution route must close the open correction target after review."), Result.CorrectionState.bCorrectionRequired);
	TestTrue(TEXT("The resolution route must mark the correction shell complete after review."), Result.CorrectionState.bCorrectionComplete);
	TestEqual(TEXT("The resolution route must retain one correction resolution in the ledger."), Result.CorrectionState.ResolutionLedger.Num(), 1);
	TestEqual(TEXT("The resolution route must count one resolved correction event."), Result.CorrectionState.ResolvedCorrectionCount, 1);
	TestTrue(TEXT("Solve guidance must unlock once the correction review closes."), Result.SolveExplanationState.bReadyForExplanation);
	TestEqual(TEXT("The solve explanation route must return to the bounded first solve stage."), Result.SolveExplanationState.RecommendedStageId, TEXT("orient-centers"));
	TestEqual(TEXT("The session must count one resolved correction event."), SessionState.ResolvedCorrectionCount, 1);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubePhase6RSCorrectionResolutionPartialClosureTest,
	"HyperTwist.Permissive.ClassicCube.Phase6R.S.CorrectionResolutionPartialClosure",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	HyperTwistClassicCubePhase6RSTestInternal::CommitClassicCubeNet(TrainingSubsystem);
	HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 7);
	HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("R"), 8);
	const FHyperTwistVisionCommitResult Result =
		HyperTwistClassicCubePhase6RSTestInternal::ResolveCorrection(TrainingSubsystem, TEXT("F"));

	const FHyperTwistTrainingRecognitionSessionState SessionState =
		TrainingSubsystem->GetActiveRecognitionSessionState();
	TestTrue(TEXT("The partial closure route must still surface an applied resolution."), Result.AppliedCorrectionResolution.IsStructurallyValid());
	TestTrue(TEXT("The partial closure route must keep correction open while another revised face remains."), Result.CorrectionState.bCorrectionRequired);
	TestFalse(TEXT("The partial closure route must not mark the shell complete while another face is pending."), Result.CorrectionState.bCorrectionComplete);
	TestEqual(TEXT("The partial closure route must leave the earlier-ordered revised face active."), Result.CorrectionState.ActiveTargetFaceId, TEXT("R"));
	TestEqual(TEXT("The partial closure route must classify the remaining active target as a revised face."), Result.CorrectionState.ActiveTarget.ReasonId, TEXT("revised-face"));
	TestEqual(TEXT("The partial closure route must preserve one ledger entry after the first accepted review."), Result.CorrectionState.ResolutionLedger.Num(), 1);
	TestFalse(TEXT("Solve guidance must stay blocked while another correction target remains."), Result.SolveExplanationState.bReadyForExplanation);
	TestEqual(TEXT("The solve explanation route must stay in cube-edit while another review remains."), Result.SolveExplanationState.RecommendedStageId, TEXT("cube-edit"));
	TestEqual(TEXT("The session must still count only one resolved correction review."), SessionState.ResolvedCorrectionCount, 1);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistClassicCubePhase6RSCorrectionResolutionReopenTest,
	"HyperTwist.Permissive.ClassicCube.Phase6R.S.CorrectionResolutionReopen",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	HyperTwistClassicCubePhase6RSTestInternal::CommitClassicCubeNet(TrainingSubsystem);
	HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 7);
	HyperTwistClassicCubePhase6RSTestInternal::ResolveCorrection(TrainingSubsystem, TEXT("F"));
	const FHyperTwistVisionCommitResult Result =
		HyperTwistClassicCubePhase6RSTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 8);

	const FHyperTwistTrainingRecognitionSessionState SessionState =
		TrainingSubsystem->GetActiveRecognitionSessionState();
	TestTrue(TEXT("The reopen route must keep a structurally valid correction state."), Result.CorrectionState.IsStructurallyValid());
	TestTrue(TEXT("The reopen route must reopen correction after a later recommit on the same face."), Result.CorrectionState.bCorrectionRequired);
	TestEqual(TEXT("The reopen route must target the recommitted face again."), Result.CorrectionState.ActiveTargetFaceId, TEXT("F"));
	TestEqual(TEXT("The reopen route must classify the reopened target as a revised face."), Result.CorrectionState.ActiveTarget.ReasonId, TEXT("revised-face"));
	TestEqual(TEXT("The reopen route must preserve the earlier accepted review in the ledger."), Result.CorrectionState.ResolutionLedger.Num(), 1);
	TestFalse(TEXT("Solve guidance must block again after a later recommit reopens correction."), Result.SolveExplanationState.bReadyForExplanation);
	TestEqual(TEXT("The session must not double-count a new resolution when only a recommit occurred."), SessionState.ResolvedCorrectionCount, 1);
	return true;
}

#endif
