// 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 HyperTwistRubixCubeSolverPhase6RPTestInternal
{
	FHyperTwistTrainingDeck MakeRecognitionDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-p/rubix-cube-solver-browser-shell");
		Deck.Title = TEXT("Phase 6R-P Rubix Cube Solver Browser Shell");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("phase6r-p-case");
		TrainingCase.PuzzleId = TEXT("cube/3x3x3");
		TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Recognition;
		TrainingCase.PromptLabel = TEXT("Phase 6R-P 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-p-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(
	FHyperTwistRubixCubeSolverPhase6RPBrowserShellSessionConfigTest,
	"HyperTwist.Permissive.RubixCubeSolver.Phase6R.P.BrowserShellSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	FString OpenError;
	TestTrue(TEXT("The recognition session must open for the browser-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 browser shell route must normalize the first-party browser profile id."),
		SessionState.SessionConfig.BrowserShellProfileId,
		TEXT("classic-cube-browser-shell-v1")
	);
	TestTrue(
		TEXT("The browser shell route must provide a structurally valid browser shell profile definition."),
		SessionState.SessionConfig.BrowserShellProfileDefinition.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The browser shell must retain three ordered browser stages."),
		SessionState.SessionConfig.BrowserShellProfileDefinition.OrderedStageIds.Num(),
		3
	);
	TestEqual(
		TEXT("The browser shell must retain four bounded browser actions."),
		SessionState.SessionConfig.BrowserShellProfileDefinition.ActionBindings.Num(),
		4
	);
	TestEqual(
		TEXT("The browser shell must keep the websocket transport kind."),
		SessionState.SessionConfig.BrowserShellProfileDefinition.TransportKind,
		TEXT("websocket")
	);
	TestEqual(
		TEXT("The browser shell must keep the local websocket endpoint."),
		SessionState.SessionConfig.BrowserShellProfileDefinition.TransportEndpoint,
		TEXT("ws://localhost:8090/")
	);
	TestEqual(
		TEXT("The browser shell must preserve the donor-like capture interval."),
		SessionState.SessionConfig.BrowserShellProfileDefinition.CaptureIntervalMs,
		100
	);
	TestTrue(
		TEXT("The browser shell must allow manual cube edit in the bounded route."),
		SessionState.SessionConfig.BrowserShellProfileDefinition.bSupportsManualCubeEdit
	);
	TestFalse(
		TEXT("The browser shell must still defer playback-stage ownership in this packet."),
		SessionState.SessionConfig.BrowserShellProfileDefinition.bSupportsPlaybackStage
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistRubixCubeSolverPhase6RPBrowserShellPreviewStateTest,
	"HyperTwist.Permissive.RubixCubeSolver.Phase6R.P.BrowserShellPreviewState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	FHyperTwistVisionFrameEnvelope Frame;
	Frame.TargetFaceHint = TEXT("F");
	const FHyperTwistVisionPreviewResult Result = TrainingSubsystem->SubmitActiveRecognitionFrame(Frame);

	TestTrue(TEXT("The preview result must carry a structurally valid browser shell state."), Result.BrowserShellState.IsStructurallyValid());
	TestEqual(TEXT("The preview browser shell must preserve the first-party profile id."), Result.BrowserShellState.BrowserShellProfileId, TEXT("classic-cube-browser-shell-v1"));
	TestEqual(TEXT("The preview browser shell must stay in the video-stream stage."), Result.BrowserShellState.ActiveStageId, TEXT("video-stream"));
	TestEqual(TEXT("The preview browser shell must keep the websocket transport kind."), Result.BrowserShellState.TransportKind, TEXT("websocket"));
	TestEqual(TEXT("The preview browser shell must keep the local websocket endpoint."), Result.BrowserShellState.TransportEndpoint, TEXT("ws://localhost:8090/"));
	TestEqual(TEXT("The preview browser shell must preserve the 100ms capture interval."), Result.BrowserShellState.CaptureIntervalMs, 100);
	TestEqual(TEXT("The preview browser shell must preserve the classic cube dimension."), Result.BrowserShellState.CubeDimension, 3);
	TestEqual(TEXT("The preview browser shell must keep the six-face expectation."), Result.BrowserShellState.ExpectedFaceCount, 6);
	TestEqual(TEXT("The preview browser shell must report no committed faces yet."), Result.BrowserShellState.CommittedFaceCount, 0);
	TestEqual(TEXT("The preview browser shell must preserve the current face hint."), Result.BrowserShellState.TargetFaceHint, TEXT("F"));
	TestTrue(TEXT("The preview browser shell must keep the transport route ready."), Result.BrowserShellState.bTransportRouteReady);
	TestTrue(TEXT("The preview browser shell must keep the video-stream surface visible."), Result.BrowserShellState.bVideoStreamVisible);
	TestFalse(TEXT("The preview browser shell must keep manual edit hidden."), Result.BrowserShellState.bManualCubeEditVisible);
	TestFalse(TEXT("The preview browser shell must keep manual edit unready."), Result.BrowserShellState.bManualCubeEditReady);
	TestTrue(TEXT("The preview browser shell must keep playback deferred."), Result.BrowserShellState.bPlaybackDeferred);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistRubixCubeSolverPhase6RPBrowserShellCompleteSessionTest,
	"HyperTwist.Permissive.RubixCubeSolver.Phase6R.P.BrowserShellCompleteSession",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	TestTrue(TEXT("The completed browser-shell route must carry a structurally valid reconstruction session."), Result.ReconstructionSession.IsStructurallyValid());
	TestTrue(TEXT("The completed browser-shell route must carry a structurally valid browser shell state."), Result.BrowserShellState.IsStructurallyValid());
	TestTrue(TEXT("The completed reconstruction must expose a full classic cube net."), Result.ReconstructionSession.bHasCompleteClassicCubeNet);
	TestEqual(TEXT("The browser shell must transition into cube-edit after a complete reconstruction."), Result.BrowserShellState.ActiveStageId, TEXT("cube-edit"));
	TestEqual(TEXT("The browser shell must report six committed faces after a complete reconstruction."), Result.BrowserShellState.CommittedFaceCount, 6);
	TestFalse(TEXT("The browser shell must hide the live video stream once manual correction is ready."), Result.BrowserShellState.bVideoStreamVisible);
	TestTrue(TEXT("The browser shell must expose manual cube edit once the reconstruction is complete."), Result.BrowserShellState.bManualCubeEditVisible);
	TestTrue(TEXT("The browser shell must mark manual cube edit ready once the reconstruction is complete."), Result.BrowserShellState.bManualCubeEditReady);
	TestTrue(TEXT("The browser shell must keep playback deferred even after the reconstruction completes."), Result.BrowserShellState.bPlaybackDeferred);
	return true;
}

#endif
