// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "Engine/GameInstance.h"
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
#include "UObject/Package.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal
{
	FHyperTwistTrainingDeck MakeSmartDeviceDeck(
		const FString& DeckId,
		const int32 CaseCount,
		const bool bInspectionEnabled
	)
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = DeckId;
		Deck.Title = TEXT("Bound3 Smart Device Deck");
		Deck.DeliveryModes = {EHyperTwistTrainingDeliveryMode::SmartDevice};
		Deck.SelectionPolicy = EHyperTwistTrainingSelectionPolicy::Uniform;
		Deck.bSupportsSmartCube = true;
		Deck.TimingPolicy.bInspectionEnabled = bInspectionEnabled;
		Deck.TimingPolicy.InspectionDurationMs = bInspectionEnabled ? 15000 : 0;
		FHyperTwistTrainingSplitPhaseDefinition FirstPhase;
		FirstPhase.PhaseId = TEXT("f2l");
		FirstPhase.Label = TEXT("F2L");
		FirstPhase.Order = 0;
		Deck.TimingPolicy.SplitPhases.Add(FirstPhase);

		FHyperTwistTrainingSplitPhaseDefinition SecondPhase;
		SecondPhase.PhaseId = TEXT("oll");
		SecondPhase.Label = TEXT("OLL");
		SecondPhase.Order = 1;
		Deck.TimingPolicy.SplitPhases.Add(SecondPhase);

		for (int32 CaseIndex = 0; CaseIndex < CaseCount; ++CaseIndex)
		{
			FHyperTwistTrainingCase TrainingCase;
			TrainingCase.CaseId = FString::Printf(TEXT("bound3-case-%d"), CaseIndex + 1);
			TrainingCase.PromptLabel = FString::Printf(TEXT("Bound3 Case %d"), CaseIndex + 1);
			TrainingCase.AllowedDeliveryModes = {EHyperTwistTrainingDeliveryMode::SmartDevice};
			Deck.Cases.Add(TrainingCase);
		}

		return Deck;
	}

	FHyperTwistTrainingSmartDeviceIdentity MakeSmartDeviceIdentity(const FString& DeviceId)
	{
		FHyperTwistTrainingSmartDeviceIdentity DeviceIdentity;
		DeviceIdentity.ProviderId = TEXT("fixture-provider");
		DeviceIdentity.DeviceId = DeviceId;
		DeviceIdentity.DeviceName = DeviceId;
		DeviceIdentity.DeviceFamily = TEXT("smart-timer");
		DeviceIdentity.CapabilityTags = {TEXT("inspection"), TEXT("splits"), TEXT("timed-attempts")};
		return DeviceIdentity;
	}

	FHyperTwistTrainingSmartDeviceEventFrame MakeEventFrame(
		const FHyperTwistTrainingSmartDeviceIdentity& DeviceIdentity,
		const FString& DeviceSessionId,
		const FString& TrainingSessionId,
		const FString& UserId,
		const FString& DeckId,
		const FString& CaseId,
		const FString& EventId,
		const int64 SequenceNumber,
		const EHyperTwistTrainingSmartDeviceEventKind EventKind
	)
	{
		FHyperTwistTrainingSmartDeviceEventFrame EventFrame;
		EventFrame.DeviceSessionId = DeviceSessionId;
		EventFrame.TrainingSessionId = TrainingSessionId;
		EventFrame.UserId = UserId;
		EventFrame.DeckId = DeckId;
		EventFrame.CaseId = CaseId;
		EventFrame.DeviceIdentity = DeviceIdentity;
		EventFrame.EventId = EventId;
		EventFrame.SequenceNumber = SequenceNumber;
		EventFrame.OccurredAtUtc = TEXT("2026-05-19T12:00:00Z");
		EventFrame.EventKind = EventKind;
		EventFrame.StatusReason = TEXT("fixture-event");
		return EventFrame;
	}

	const FHyperTwistTrainingSmartDeviceSessionRecord* FindSessionRecordById(
		const FHyperTwistTrainingRepositoryState& RepositoryState,
		const FString& DeviceSessionId
	)
	{
		return RepositoryState.StoredSmartDeviceSessions.FindByPredicate(
			[&DeviceSessionId](const FHyperTwistTrainingSmartDeviceSessionRecord& SessionRecord)
			{
				return SessionRecord.DeviceSessionId == DeviceSessionId;
			}
		);
	}

	const FHyperTwistTrainingSmartDeviceEventFrame* FindEventFrameById(
		const FHyperTwistTrainingRepositoryState& RepositoryState,
		const FString& EventId
	)
	{
		return RepositoryState.StoredSmartDeviceEventFrames.FindByPredicate(
			[&EventId](const FHyperTwistTrainingSmartDeviceEventFrame& EventFrame)
			{
				return EventFrame.EventId == EventId;
			}
		);
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistCubeDeskBound3FixtureIngestionTest,
	"HyperTwist.CleanRoom.CubeDesk.Bound3.FixtureIngestion",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistCubeDeskBound3FixtureIngestionTest::RunTest(const FString& Parameters)
{
	UGameInstance* GameInstance = NewObject<UGameInstance>();
	TestNotNull(TEXT("Game instance must exist for the smart-device subsystem test."), GameInstance);
	if (GameInstance == nullptr)
	{
		return false;
	}

	UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
	TestNotNull(TEXT("Training subsystem must be created."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	const FHyperTwistTrainingDeck Deck =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeSmartDeviceDeck(
			TEXT("bound3-fixture-deck"),
			1,
			true
		);
	const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
		Deck,
		TEXT("bound3-user"),
		TEXT("bound3-fixture-session"),
		EHyperTwistTrainingDeliveryMode::SmartDevice
	);
	TestTrue(TEXT("A smart-device run must start successfully."), RunState.IsStructurallyValid());
	TestTrue(
		TEXT("A smart-device run must select an initial case for fixture ingestion."),
		RunState.CurrentSelection.TrainingCase.IsStructurallyValid()
	);
	if (!RunState.IsStructurallyValid() || !RunState.CurrentSelection.TrainingCase.IsStructurallyValid())
	{
		return false;
	}

	const FHyperTwistTrainingSmartDeviceIdentity DeviceIdentity =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeSmartDeviceIdentity(TEXT("fixture-device-1"));
	const FHyperTwistTrainingSmartDeviceSessionRecord DeviceSession =
		TrainingSubsystem->StartActiveSmartDeviceSession(DeviceIdentity, TEXT("bound3-device-session"));
	TestTrue(TEXT("The smart-device session must start successfully."), DeviceSession.IsStructurallyValid());
	TestTrue(TEXT("The subsystem must track the active smart-device session."), TrainingSubsystem->HasActiveSmartDeviceSession());
	if (!DeviceSession.IsStructurallyValid())
	{
		return false;
	}

		const FString CaseId = RunState.CurrentSelection.TrainingCase.CaseId;
	FHyperTwistTrainingSmartDeviceEventFrame InspectionStarted =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeEventFrame(
			DeviceIdentity,
			DeviceSession.DeviceSessionId,
			RunState.Session.TrainingSessionId,
			RunState.Session.UserId,
			RunState.Session.DeckId,
			CaseId,
			TEXT("fixture-inspection"),
			1,
			EHyperTwistTrainingSmartDeviceEventKind::InspectionStarted
		);
	const FHyperTwistTrainingSmartDeviceIngestionResult InspectionResult =
		TrainingSubsystem->IngestSmartDeviceEventFrame(InspectionStarted);
	TestTrue(TEXT("Inspection-start events must be accepted."), InspectionResult.bAccepted);
	TestTrue(TEXT("Inspection-start events must update timer state."), InspectionResult.bUpdatedTimerState);
	TestEqual(
		TEXT("Inspection-start events must place the live timer in inspection."),
		TrainingSubsystem->GetActiveLiveTimerState().Phase,
		EHyperTwistTrainingLiveTimerPhase::Inspection
	);

	FHyperTwistTrainingSmartDeviceEventFrame SolveStarted =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeEventFrame(
			DeviceIdentity,
			DeviceSession.DeviceSessionId,
			RunState.Session.TrainingSessionId,
			RunState.Session.UserId,
			RunState.Session.DeckId,
			CaseId,
			TEXT("fixture-solve-start"),
			2,
			EHyperTwistTrainingSmartDeviceEventKind::SolveStarted
		);
	const FHyperTwistTrainingSmartDeviceIngestionResult SolveResult =
		TrainingSubsystem->IngestSmartDeviceEventFrame(SolveStarted);
	TestTrue(TEXT("Solve-start events must be accepted."), SolveResult.bAccepted);
	TestEqual(
		TEXT("Solve-start events must advance the live timer into solving."),
		TrainingSubsystem->GetActiveLiveTimerState().Phase,
		EHyperTwistTrainingLiveTimerPhase::Solving
	);

	FHyperTwistTrainingSmartDeviceEventFrame SplitCaptured =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeEventFrame(
			DeviceIdentity,
			DeviceSession.DeviceSessionId,
			RunState.Session.TrainingSessionId,
			RunState.Session.UserId,
			RunState.Session.DeckId,
			CaseId,
			TEXT("fixture-split"),
			3,
			EHyperTwistTrainingSmartDeviceEventKind::SplitCaptured
		);
	FHyperTwistTrainingSplitCapture FirstSplitCapture;
	FirstSplitCapture.PhaseId = TEXT("f2l");
	FirstSplitCapture.Label = TEXT("F2L");
	FirstSplitCapture.TimestampMs = 1200;
	SplitCaptured.SplitCaptures.Add(FirstSplitCapture);
	const FHyperTwistTrainingSmartDeviceIngestionResult SplitResult =
		TrainingSubsystem->IngestSmartDeviceEventFrame(SplitCaptured);
	TestTrue(TEXT("Split-captured events must be accepted."), SplitResult.bAccepted);
	TestEqual(
		TEXT("Split-captured events must retain one live-timer split capture."),
		TrainingSubsystem->GetActiveLiveTimerState().SplitCaptures.Num(),
		1
	);

	FHyperTwistTrainingSmartDeviceEventFrame AttemptFinished =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeEventFrame(
			DeviceIdentity,
			DeviceSession.DeviceSessionId,
			RunState.Session.TrainingSessionId,
			RunState.Session.UserId,
			RunState.Session.DeckId,
			CaseId,
			TEXT("fixture-attempt-finished"),
			4,
			EHyperTwistTrainingSmartDeviceEventKind::AttemptFinished
		);
	AttemptFinished.AttemptResult = EHyperTwistTrainingAttemptResult::Success;
	AttemptFinished.TotalTimeMs = 3210;
	AttemptFinished.RawSolveTimeMs = 3010;
	AttemptFinished.InspectionElapsedMs = 1500;
	AttemptFinished.SplitCaptures = SplitCaptured.SplitCaptures;
	const FHyperTwistTrainingSmartDeviceIngestionResult AttemptResult =
		TrainingSubsystem->IngestSmartDeviceEventFrame(AttemptFinished);
	TestTrue(TEXT("Attempt-finished events must be accepted."), AttemptResult.bAccepted);
	TestTrue(TEXT("Attempt-finished events must submit a training attempt."), AttemptResult.bSubmittedAttempt);
	TestTrue(
		TEXT("Attempt-finished events must return a linked attempt id."),
		!AttemptResult.LinkedAttemptId.IsEmpty()
	);

	const FHyperTwistTrainingRepositoryState RepositoryState = TrainingSubsystem->GetTrainingRepositoryState();
	TestEqual(
		TEXT("Fixture ingestion must persist all accepted device events."),
		RepositoryState.StoredSmartDeviceEventFrames.Num(),
		4
	);
	TestEqual(
		TEXT("Fixture ingestion must persist exactly one device session."),
		RepositoryState.StoredSmartDeviceSessions.Num(),
		1
	);
	TestEqual(
		TEXT("Fixture ingestion must persist exactly one smart-device attempt."),
		TrainingSubsystem->GetActiveRunState().Attempts.Num(),
		1
	);

	const FHyperTwistTrainingSmartDeviceSessionRecord* StoredSessionRecord =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::FindSessionRecordById(
			RepositoryState,
			DeviceSession.DeviceSessionId
		);
	TestNotNull(TEXT("The stored smart-device session record must exist."), StoredSessionRecord);
	if (StoredSessionRecord == nullptr)
	{
		return false;
	}

	TestEqual(
		TEXT("The stored smart-device session must retain one linked attempt."),
		StoredSessionRecord->LinkedAttemptCount,
		1
	);
	TestEqual(
		TEXT("The stored smart-device session must retain all accepted event frames."),
		StoredSessionRecord->AcceptedEventCount,
		4
	);

	const FHyperTwistTrainingSmartDeviceEventFrame* StoredAttemptEvent =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::FindEventFrameById(
			RepositoryState,
			TEXT("fixture-attempt-finished")
		);
	TestNotNull(TEXT("The stored attempt-finished event must exist."), StoredAttemptEvent);
	if (StoredAttemptEvent == nullptr)
	{
		return false;
	}

	TestEqual(
		TEXT("The stored attempt-finished event must retain its linked attempt id."),
		StoredAttemptEvent->LinkedAttemptId,
		AttemptResult.LinkedAttemptId
	);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistCubeDeskBound3DeviceSessionLifecycleTest,
	"HyperTwist.CleanRoom.CubeDesk.Bound3.DeviceSessionLifecycle",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistCubeDeskBound3DeviceSessionLifecycleTest::RunTest(const FString& Parameters)
{
	UGameInstance* GameInstance = NewObject<UGameInstance>();
	TestNotNull(TEXT("Game instance must exist for the device-session lifecycle test."), GameInstance);
	if (GameInstance == nullptr)
	{
		return false;
	}

	UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
	TestNotNull(TEXT("Training subsystem must be created."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	const FHyperTwistTrainingDeck Deck =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeSmartDeviceDeck(
			TEXT("bound3-lifecycle-deck"),
			1,
			false
		);
	const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
		Deck,
		TEXT("bound3-user"),
		TEXT("bound3-lifecycle-session"),
		EHyperTwistTrainingDeliveryMode::SmartDevice
	);
	TestTrue(TEXT("A smart-device lifecycle run must start successfully."), RunState.IsStructurallyValid());
	if (!RunState.IsStructurallyValid())
	{
		return false;
	}

	const FHyperTwistTrainingSmartDeviceIdentity DeviceIdentity =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeSmartDeviceIdentity(TEXT("fixture-device-2"));
	const FHyperTwistTrainingSmartDeviceSessionRecord DeviceSession =
		TrainingSubsystem->StartActiveSmartDeviceSession(DeviceIdentity, TEXT("bound3-lifecycle-device-session"));
	TestTrue(TEXT("The smart-device session must start successfully."), DeviceSession.IsStructurallyValid());
	TestTrue(TEXT("The subsystem must report the session as active after start."), TrainingSubsystem->HasActiveSmartDeviceSession());
	if (!DeviceSession.IsStructurallyValid())
	{
		return false;
	}

	FHyperTwistTrainingSmartDeviceEventFrame Connected =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeEventFrame(
			DeviceIdentity,
			DeviceSession.DeviceSessionId,
			RunState.Session.TrainingSessionId,
			RunState.Session.UserId,
			RunState.Session.DeckId,
			RunState.CurrentSelection.TrainingCase.CaseId,
			TEXT("lifecycle-connected"),
			1,
			EHyperTwistTrainingSmartDeviceEventKind::Connected
		);
	const FHyperTwistTrainingSmartDeviceIngestionResult ConnectedResult =
		TrainingSubsystem->IngestSmartDeviceEventFrame(Connected);
	TestTrue(TEXT("Connected events must be accepted."), ConnectedResult.bAccepted);

	FHyperTwistTrainingSmartDeviceEventFrame Disconnected =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeEventFrame(
			DeviceIdentity,
			DeviceSession.DeviceSessionId,
			RunState.Session.TrainingSessionId,
			RunState.Session.UserId,
			RunState.Session.DeckId,
			RunState.CurrentSelection.TrainingCase.CaseId,
			TEXT("lifecycle-disconnected"),
			2,
			EHyperTwistTrainingSmartDeviceEventKind::Disconnected
		);
	const FHyperTwistTrainingSmartDeviceIngestionResult DisconnectedResult =
		TrainingSubsystem->IngestSmartDeviceEventFrame(Disconnected);
	TestTrue(TEXT("Disconnected events must be accepted."), DisconnectedResult.bAccepted);
	TestFalse(
		TEXT("The subsystem must stop reporting the session as active after disconnect."),
		TrainingSubsystem->HasActiveSmartDeviceSession()
	);

	const FHyperTwistTrainingRepositoryState RepositoryState = TrainingSubsystem->GetTrainingRepositoryState();
	const FHyperTwistTrainingSmartDeviceSessionRecord* StoredSessionRecord =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::FindSessionRecordById(
			RepositoryState,
			DeviceSession.DeviceSessionId
		);
	TestNotNull(TEXT("The disconnected smart-device session must remain stored."), StoredSessionRecord);
	if (StoredSessionRecord == nullptr)
	{
		return false;
	}

	TestEqual(
		TEXT("The stored session must resolve to the disconnected lifecycle state."),
		StoredSessionRecord->ConnectionState,
		EHyperTwistTrainingSmartDeviceConnectionState::Disconnected
	);
	TestTrue(
		TEXT("The stored session must retain an ended-at timestamp after disconnect."),
		!StoredSessionRecord->EndedAtUtc.IsEmpty()
	);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistCubeDeskBound3RepositoryDedupeTest,
	"HyperTwist.CleanRoom.CubeDesk.Bound3.RepositoryDedupe",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistCubeDeskBound3RepositoryDedupeTest::RunTest(const FString& Parameters)
{
	UGameInstance* GameInstance = NewObject<UGameInstance>();
	TestNotNull(TEXT("Game instance must exist for the repository dedupe test."), GameInstance);
	if (GameInstance == nullptr)
	{
		return false;
	}

	UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
	TestNotNull(TEXT("Training subsystem must be created."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	const FHyperTwistTrainingDeck Deck =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeSmartDeviceDeck(
			TEXT("bound3-dedupe-deck"),
			1,
			false
		);
	const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
		Deck,
		TEXT("bound3-user"),
		TEXT("bound3-dedupe-session"),
		EHyperTwistTrainingDeliveryMode::SmartDevice
	);
	TestTrue(TEXT("A smart-device dedupe run must start successfully."), RunState.IsStructurallyValid());
	if (!RunState.IsStructurallyValid())
	{
		return false;
	}

	const FHyperTwistTrainingSmartDeviceIdentity DeviceIdentity =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeSmartDeviceIdentity(TEXT("fixture-device-3"));
	const FHyperTwistTrainingSmartDeviceSessionRecord DeviceSession =
		TrainingSubsystem->StartActiveSmartDeviceSession(DeviceIdentity, TEXT("bound3-dedupe-device-session"));
	TestTrue(TEXT("The smart-device session must start successfully."), DeviceSession.IsStructurallyValid());
	if (!DeviceSession.IsStructurallyValid())
	{
		return false;
	}

	FHyperTwistTrainingSmartDeviceEventFrame AttemptFinished =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::MakeEventFrame(
			DeviceIdentity,
			DeviceSession.DeviceSessionId,
			RunState.Session.TrainingSessionId,
			RunState.Session.UserId,
			RunState.Session.DeckId,
			RunState.CurrentSelection.TrainingCase.CaseId,
			TEXT("dedupe-attempt-finished"),
			1,
			EHyperTwistTrainingSmartDeviceEventKind::AttemptFinished
		);
	AttemptFinished.AttemptResult = EHyperTwistTrainingAttemptResult::Success;
	AttemptFinished.TotalTimeMs = 2450;
	AttemptFinished.RawSolveTimeMs = 2250;
	const FHyperTwistTrainingSmartDeviceIngestionResult FirstAttemptResult =
		TrainingSubsystem->IngestSmartDeviceEventFrame(AttemptFinished);
	TestTrue(TEXT("The first attempt-finished event must be accepted."), FirstAttemptResult.bAccepted);
	TestTrue(TEXT("The first attempt-finished event must submit an attempt."), FirstAttemptResult.bSubmittedAttempt);

	const FHyperTwistTrainingSmartDeviceIngestionResult DuplicateAttemptResult =
		TrainingSubsystem->IngestSmartDeviceEventFrame(AttemptFinished);
	TestTrue(TEXT("Duplicate attempt-finished events must be recognized as duplicates."), DuplicateAttemptResult.bDuplicate);

	const FHyperTwistTrainingRepositoryState RepositoryState = TrainingSubsystem->GetTrainingRepositoryState();
	TestEqual(
		TEXT("Idempotent dedupe must retain only one stored smart-device event frame."),
		RepositoryState.StoredSmartDeviceEventFrames.Num(),
		1
	);
	TestEqual(
		TEXT("Idempotent dedupe must retain only one submitted attempt in the run state."),
		TrainingSubsystem->GetActiveRunState().Attempts.Num(),
		1
	);

	const FHyperTwistTrainingSmartDeviceSessionRecord* StoredSessionRecord =
		HyperTwistCubeDeskBound3SmartDeviceBridgeTestInternal::FindSessionRecordById(
			RepositoryState,
			DeviceSession.DeviceSessionId
		);
	TestNotNull(TEXT("The stored smart-device session must exist after dedupe."), StoredSessionRecord);
	if (StoredSessionRecord == nullptr)
	{
		return false;
	}

	TestEqual(
		TEXT("The stored smart-device session must count one duplicate event."),
		StoredSessionRecord->DuplicateEventCount,
		1
	);

	const FHyperTwistTrainingRepositoryRoundTripVerification Verification =
		UHyperTwistTrainingPersistenceLibrary::VerifyTrainingRepositoryStateRoundTrip(
			RepositoryState,
			TEXT("Automation/HyperTwistCubeDeskBound3RepositoryRoundTrip.json")
		);
	TestTrue(TEXT("Repository roundtrip verification must pass after smart-device dedupe."), Verification.bDidRoundTripPass);
	TestEqual(
		TEXT("Roundtrip verification must retain one smart-device session."),
		Verification.LoadedSmartDeviceSessionCount,
		1
	);
	TestEqual(
		TEXT("Roundtrip verification must retain one smart-device event frame."),
		Verification.LoadedSmartDeviceEventFrameCount,
		1
	);

	return true;
}

#endif
