// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "Engine/GameInstance.h"
#include "HyperTwistCore/HyperTwistCoreLibrary.h"
#include "HyperTwistCore/HyperTwistCoreTypes.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
#include "JsonObjectConverter.h"
#include "UObject/UnrealType.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistRubixCubeSolverPhase6RCTestInternal
{
	FHyperTwistTrainingDeck MakeRecognitionDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-c/rubix-cube-solver-recognition");
		Deck.Title = TEXT("Phase 6R-C Rubix Cube Solver Recognition");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("phase6r-c-case");
		TrainingCase.PuzzleId = TEXT("cube/3x3x3");
		TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Recognition;
		TrainingCase.PromptLabel = TEXT("Phase 6R-C 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-c-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 FHyperTwistVisionCommittedFaceState* FindCommittedFace(
		const FHyperTwistVisionReconstructionSession& Session,
		const FString& FaceId
	)
	{
		return Session.CommittedFaces.FindByPredicate(
			[&FaceId](const FHyperTwistVisionCommittedFaceState& Candidate)
			{
				return Candidate.FaceId.Equals(FaceId, ESearchCase::IgnoreCase);
			}
		);
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistRubixCubeSolverPhase6RCCommitBuildsReconstructionSessionTest,
	"HyperTwist.Permissive.RubixCubeSolver.Phase6R.C.CommitBuildsReconstructionSession",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

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

	TestTrue(TEXT("The commit result must carry a structurally valid reconstruction session."), Result.ReconstructionSession.IsStructurallyValid());
	TestEqual(TEXT("The first face commit must keep one committed face."), Result.ReconstructionSession.CommittedFaces.Num(), 1);
	TestEqual(TEXT("The first face commit must report five missing faces."), Result.ReconstructionSession.MissingFaces.Num(), 5);
	TestEqual(TEXT("The reconstruction session must keep the committed face id."), Result.ReconstructionSession.CommittedFaces[0].FaceId, TEXT("F"));
	TestFalse(TEXT("An incomplete reconstruction session must not expose a full snapshot yet."), Result.Snapshot.IsStructurallyValid());

	const FHyperTwistTrainingRecognitionSessionState SessionState =
		TrainingSubsystem->GetActiveRecognitionSessionState();
	TestTrue(TEXT("The active recognition state must keep the same reconstruction session."), SessionState.ActiveReconstructionSession.IsStructurallyValid());
	TestEqual(TEXT("The active recognition state must track one committed face."), SessionState.ActiveReconstructionSession.CommittedFaces.Num(), 1);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistRubixCubeSolverPhase6RCRevisionLedgerTest,
	"HyperTwist.Permissive.RubixCubeSolver.Phase6R.C.RevisionLedger",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	HyperTwistRubixCubeSolverPhase6RCTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 1);
	const FHyperTwistVisionCommitResult RevisionResult =
		HyperTwistRubixCubeSolverPhase6RCTestInternal::CommitFace(TrainingSubsystem, TEXT("F"), 2);

	const FHyperTwistVisionCommittedFaceState* FaceState =
		HyperTwistRubixCubeSolverPhase6RCTestInternal::FindCommittedFace(RevisionResult.ReconstructionSession, TEXT("F"));
	TestNotNull(TEXT("The revised reconstruction session must still track the front face."), FaceState);
	if (FaceState == nullptr)
	{
		return false;
	}

	TestEqual(TEXT("A second face commit must increment the observation count."), FaceState->ObservationCount, 2);
	TestEqual(TEXT("A second face commit must increment the revision count."), FaceState->RevisionCount, 1);
	TestEqual(TEXT("The revision ledger must preserve one total revision in the session."), RevisionResult.ReconstructionSession.TotalRevisionCount, 1);
	TestEqual(TEXT("The first sticker vote must record two votes after a recommit."), FaceState->StickerVotes[0].WinningVoteCount, 2);
	TestFalse(TEXT("A revised but incomplete reconstruction session must still not expose a full snapshot."), RevisionResult.Snapshot.IsStructurallyValid());

	const FHyperTwistTrainingRecognitionSessionState SessionState =
		TrainingSubsystem->GetActiveRecognitionSessionState();
	TestEqual(TEXT("A recommit must count as one correction event."), SessionState.CorrectionEventCount, 1);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistRubixCubeSolverPhase6RCFinalizeBuildsClassicNetTest,
	"HyperTwist.Permissive.RubixCubeSolver.Phase6R.C.FinalizeBuildsClassicNet",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	const TArray<FString> FaceOrder = {
		TEXT("U"),
		TEXT("R"),
		TEXT("F"),
		TEXT("D"),
		TEXT("L"),
		TEXT("B")
	};
	for (int32 FaceOrdinal = 0; FaceOrdinal < FaceOrder.Num(); ++FaceOrdinal)
	{
		HyperTwistRubixCubeSolverPhase6RCTestInternal::CommitFace(
			TrainingSubsystem,
			FaceOrder[FaceOrdinal],
			FaceOrdinal + 1
		);
	}

	const FHyperTwistVisionFinalizeResult Result = TrainingSubsystem->FinalizeActiveRecognitionSession();

	TestTrue(TEXT("The finalized session must carry a structurally valid reconstruction session."), Result.ReconstructionSession.IsStructurallyValid());
	TestTrue(TEXT("The finalized session must carry a complete classic cube net."), Result.ReconstructionSession.bHasCompleteClassicCubeNet);
	TestTrue(TEXT("A complete reconstruction session must emit a structurally valid final state."), Result.FinalState.IsStructurallyValid());
	TestTrue(TEXT("A complete reconstruction session must emit a structurally valid final snapshot."), Result.FinalSnapshot.IsStructurallyValid());
	TestTrue(TEXT("The reconstructed final state should resolve as solved for the mock color net."), UHyperTwistCoreLibrary::IsSolved(Result.FinalState));
	TestTrue(TEXT("The bounded finalize path should not need normalization warnings for a complete mock route."), Result.NormalizationWarnings.IsEmpty());

	FHyperTwistClassicFaceletSnapshotState FaceletState;
	TestTrue(
		TEXT("The final facelet payload must deserialize."),
		FJsonObjectConverter::JsonObjectStringToUStruct(Result.FinalState.StateEncoding.PayloadJson, &FaceletState, 0, 0)
	);
	TestEqual(TEXT("The up face must keep nine stickers."), FaceletState.U.Num(), 9);
	TestEqual(TEXT("The right face must keep nine stickers."), FaceletState.R.Num(), 9);
	TestEqual(TEXT("The front face must keep the green face token."), FaceletState.F[0], TEXT("G"));
	TestEqual(TEXT("The back face must keep the blue face token."), FaceletState.B[0], TEXT("B"));

	const FHyperTwistTrainingRecognitionSessionState SessionState =
		TrainingSubsystem->GetActiveRecognitionSessionState();
	TestFalse(TEXT("The active recognition session must be closed after finalization."), SessionState.bSessionOpen);
	TestTrue(TEXT("The last finalize result must keep the complete reconstruction session."), SessionState.LastFinalizeResult.ReconstructionSession.bHasCompleteClassicCubeNet);
	return true;
}

#endif
