// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
#include "HyperTwistMemory/HyperTwistMemoryCoreLibrary.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistMemoryPhase6RM2TestInternal
{
	const FHyperTwistMemoryChronicleEventEntry* FindChronicleEventByKind(
		const FHyperTwistMemoryChronicleContinuityState& ChronicleContinuityState,
		const FString& EventKind
	)
	{
		for (const FHyperTwistMemoryChronicleEventEntry& ChronicleEvent :
			ChronicleContinuityState.ChronicleEvents)
		{
			if (ChronicleEvent.EventKind == EventKind)
			{
				return &ChronicleEvent;
			}
		}

		return nullptr;
	}

	const FHyperTwistMemoryResumePack* FindResumePackByKind(
		const FHyperTwistMemoryChronicleContinuityState& ChronicleContinuityState,
		const FString& PackKind
	)
	{
		for (const FHyperTwistMemoryResumePack& ResumePack :
			ChronicleContinuityState.ResumePacks)
		{
			if (ResumePack.PackKind == PackKind)
			{
				return &ResumePack;
			}
		}

		return nullptr;
	}

	const FHyperTwistMemoryContinuityGuard* FindGuardByKind(
		const FHyperTwistMemoryChronicleContinuityState& ChronicleContinuityState,
		const FString& GuardKind
	)
	{
		for (const FHyperTwistMemoryContinuityGuard& ContinuityGuard :
			ChronicleContinuityState.ContinuityGuards)
		{
			if (ContinuityGuard.GuardKind == GuardKind)
			{
				return &ContinuityGuard;
			}
		}

		return nullptr;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMemoryPhase6RM2ChronicleGroupingTest,
	"HyperTwist.FirstParty.Memory.Phase6R.M2.ChronicleGrouping",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMemoryPhase6RM2ChronicleGroupingTest::RunTest(const FString& Parameters)
{
	const FHyperTwistMemoryChronicleContinuityState ChronicleContinuityState =
		UHyperTwistContractLibrary::MakeSampleTrainingMemoryChronicleContinuityState();

	TestTrue(
		TEXT("The Phase 6R-M2 chronicle/continuity state must be structurally valid."),
		ChronicleContinuityState.IsStructurallyValid()
	);
	TestTrue(
		TEXT("The Phase 6R-M2 state must expose chronicle capture events."),
		ChronicleContinuityState.ChronicleCaptureEventCount > 0
	);
	TestTrue(
		TEXT("The Phase 6R-M2 state must expose continuity resume events."),
		ChronicleContinuityState.ContinuityResumeEventCount > 0
	);
	TestTrue(
		TEXT("The Phase 6R-M2 state must expose grouped continuity sessions."),
		ChronicleContinuityState.SessionGroups.Num() >= 3
	);
	TestTrue(
		TEXT("The Phase 6R-M2 state must expose bounded resume packs."),
		ChronicleContinuityState.ResumePacks.Num() >= 2
	);
	TestEqual(
		TEXT("The canonical sample should not emit continuity guards."),
		ChronicleContinuityState.ContinuityGuards.Num(),
		0
	);

	const FHyperTwistMemoryChronicleEventEntry* RunEvent =
		HyperTwistMemoryPhase6RM2TestInternal::FindChronicleEventByKind(
			ChronicleContinuityState,
			TEXT("training-run-record")
		);
	TestNotNull(TEXT("A training run-record chronicle event must exist."), RunEvent);
	if (RunEvent != nullptr)
	{
		TestEqual(
			TEXT("The run-record chronicle event must stay in the chronicle lane."),
			RunEvent->Lane,
			EHyperTwistMemoryLane::ChronicleCapture
		);
	}

	const FHyperTwistMemoryResumePack* ReviewResumePack =
		HyperTwistMemoryPhase6RM2TestInternal::FindResumePackByKind(
			ChronicleContinuityState,
			TEXT("review-plan-resume")
		);
	TestNotNull(TEXT("A review-plan resume pack must exist."), ReviewResumePack);
	if (ReviewResumePack != nullptr)
	{
		TestEqual(
			TEXT("The review resume pack must stay in the continuity lane."),
			ReviewResumePack->Lane,
			EHyperTwistMemoryLane::ContinuityResume
		);
		TestTrue(
			TEXT("The review resume pack must preserve a grouped chronicle link."),
			ReviewResumePack->ChronicleGroupIds.Num() > 0
		);
	}

	const FHyperTwistMemoryResumePack* QueueResumePack =
		HyperTwistMemoryPhase6RM2TestInternal::FindResumePackByKind(
			ChronicleContinuityState,
			TEXT("coach-session-queue-resume")
		);
	TestNotNull(TEXT("A coach-session-queue resume pack must exist."), QueueResumePack);
	if (QueueResumePack != nullptr)
	{
		TestTrue(
			TEXT("The queue resume pack must keep pending continuity work."),
			QueueResumePack->PendingItemCount > 0
		);
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMemoryPhase6RM2ContinuityGuardsTest,
	"HyperTwist.FirstParty.Memory.Phase6R.M2.ContinuityGuards",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMemoryPhase6RM2ContinuityGuardsTest::RunTest(const FString& Parameters)
{
	FHyperTwistTrainingRepositoryState RepositoryState =
		UHyperTwistContractLibrary::MakeSampleTrainingRepositoryState();
	const FString UserId = RepositoryState.RunRecords.Num() > 0
		? RepositoryState.RunRecords[0].Session.UserId
		: FString();
	TestFalse(TEXT("The continuity-guard sample must resolve a user id."), UserId.IsEmpty());
	if (UserId.IsEmpty())
	{
		return false;
	}

	const FHyperTwistTrainingCoachActionPlan CoachActionPlan =
		UHyperTwistContractLibrary::MakeSampleTrainingCoachActionPlan();
	if (CoachActionPlan.IsStructurallyValid())
	{
		RepositoryState.StoredCoachActionPlans.Add(CoachActionPlan);

		FHyperTwistTrainingCoachActionPlan BrokenLineagePlan = CoachActionPlan;
		BrokenLineagePlan.PlanId = TEXT("coach_plan_broken_lineage");
		BrokenLineagePlan.ParentPlanId = TEXT("missing_parent_plan");
		BrokenLineagePlan.RootPlanId = TEXT("missing_root_plan");
		BrokenLineagePlan.SourceSessionId = TEXT("missing_training_session");
		BrokenLineagePlan.SourceReviewPlanId = TEXT("missing_review_plan");
		BrokenLineagePlan.FollowUpDepth = 3;
		RepositoryState.StoredCoachActionPlans.Add(BrokenLineagePlan);
	}

	const FHyperTwistTrainingCoachSessionQueueState QueueState =
		UHyperTwistContractLibrary::MakeSampleTrainingCoachSessionQueueState();
	TestTrue(TEXT("The queue sample must be structurally valid."), QueueState.IsStructurallyValid());
	if (!QueueState.IsStructurallyValid())
	{
		return false;
	}

	RepositoryState.StoredCoachSessionQueues.Add(QueueState);

	FHyperTwistTrainingCoachSessionQueueState BrokenQueueState = QueueState;
	BrokenQueueState.QueueId = TEXT("coach_queue_broken");
	BrokenQueueState.ActiveEntryId = TEXT("missing_active_entry");
	BrokenQueueState.ActiveTrainingSessionId = TEXT("missing_training_session");
	if (BrokenQueueState.Entries.Num() > 0)
	{
		BrokenQueueState.Entries[0].EntryState =
			EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
		BrokenQueueState.Entries[0].SourceTrainingSessionId = TEXT("missing_training_session");
		BrokenQueueState.Entries[0].StartedAtUtc = TEXT("2026-04-28T12:15:00Z");
	}
	RepositoryState.StoredCoachSessionQueues.Add(BrokenQueueState);

	FHyperTwistTrainingCoachSessionQueueHistoryEntry BrokenHistoryEntry;
	BrokenHistoryEntry.HistoryEntryId = TEXT("history_missing_training_session");
	BrokenHistoryEntry.QueueId = BrokenQueueState.QueueId;
	BrokenHistoryEntry.UserId = BrokenQueueState.UserId;
	BrokenHistoryEntry.EntryId = BrokenQueueState.Entries.Num() > 0
		? BrokenQueueState.Entries[0].EntryId
		: TEXT("missing_entry");
	BrokenHistoryEntry.SourceLabel = BrokenQueueState.Entries.Num() > 0
		? BrokenQueueState.Entries[0].SourceLabel
		: TEXT("coach_queue_primary");
	BrokenHistoryEntry.WorkFingerprint = BrokenQueueState.Entries.Num() > 0
		? BrokenQueueState.Entries[0].WorkFingerprint
		: TEXT("broken_history_work");
	BrokenHistoryEntry.EventKind = EHyperTwistTrainingCoachSessionQueueEventKind::Started;
	BrokenHistoryEntry.EventAtUtc = TEXT("2026-04-28T12:15:00Z");
	BrokenHistoryEntry.PreviousEntryState =
		EHyperTwistTrainingCoachSessionQueueEntryState::Ready;
	BrokenHistoryEntry.NewEntryState =
		EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
	BrokenHistoryEntry.SourceTrainingSessionId = TEXT("missing_training_session");
	BrokenHistoryEntry.FocusDeckId = BrokenQueueState.FocusDeckId;
	BrokenHistoryEntry.FocusMethodSegmentId = BrokenQueueState.FocusMethodSegmentId;
	if (BrokenQueueState.Entries.Num() > 0)
	{
		BrokenHistoryEntry.FocusCaseIds = BrokenQueueState.Entries[0].FocusCaseIds;
	}
	RepositoryState.StoredCoachSessionQueueHistory.Add(BrokenHistoryEntry);

	FHyperTwistTrainingCoachSessionQueueHistoryEntry OrphanedEntryHistoryEntry =
		BrokenHistoryEntry;
	OrphanedEntryHistoryEntry.HistoryEntryId = TEXT("history_missing_entry_link");
	OrphanedEntryHistoryEntry.QueueId = QueueState.QueueId;
	OrphanedEntryHistoryEntry.EntryId = TEXT("missing_entry_link");
	RepositoryState.StoredCoachSessionQueueHistory.Add(OrphanedEntryHistoryEntry);

	const FHyperTwistMemoryChronicleContinuityState ChronicleContinuityState =
		UHyperTwistMemoryCoreLibrary::DeriveMemoryChronicleContinuityState(
			RepositoryState,
			UserId,
			TEXT("2026-04-28T12:40:00Z")
		);

	TestTrue(
		TEXT("The continuity-guard derivation must remain structurally valid."),
		ChronicleContinuityState.IsStructurallyValid()
	);
	TestTrue(
		TEXT("Broken continuity inputs must emit repository continuity guards."),
		ChronicleContinuityState.bHasRepositoryContinuityIssues
	);
	TestTrue(
		TEXT("Broken continuity inputs must emit at least one blocking guard."),
		ChronicleContinuityState.BlockingGuardCount > 0
	);

	const FHyperTwistMemoryContinuityGuard* QueueGuard =
		HyperTwistMemoryPhase6RM2TestInternal::FindGuardByKind(
			ChronicleContinuityState,
			TEXT("coach-session-queue")
		);
	TestNotNull(TEXT("A coach-session-queue continuity guard must exist."), QueueGuard);
	if (QueueGuard != nullptr)
	{
		TestEqual(
			TEXT("The queue continuity guard must remain blocking."),
			QueueGuard->Severity,
			EHyperTwistMemoryContinuityGuardSeverity::Blocking
		);
	}

	const FHyperTwistMemoryResumePack* QueueResumePack =
		HyperTwistMemoryPhase6RM2TestInternal::FindResumePackByKind(
			ChronicleContinuityState,
			TEXT("coach-session-queue-resume")
		);
	TestNotNull(TEXT("A queue resume pack must still be produced."), QueueResumePack);
	if (QueueResumePack != nullptr)
	{
		TestTrue(
			TEXT("Queue continuity issues must mark the queue resume pack for guard review."),
			QueueResumePack->bNeedsContinuityGuard
		);
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMemoryPhase6RM2SerializationRoundTripTest,
	"HyperTwist.FirstParty.Memory.Phase6R.M2.SerializationRoundTrip",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMemoryPhase6RM2SerializationRoundTripTest::RunTest(const FString& Parameters)
{
	const FHyperTwistMemoryChronicleContinuityState ChronicleContinuityState =
		UHyperTwistContractLibrary::MakeSampleTrainingMemoryChronicleContinuityState();
	TestTrue(
		TEXT("The Phase 6R-M2 chronicle/continuity state must be structurally valid."),
		ChronicleContinuityState.IsStructurallyValid()
	);

	const FString Json =
		UHyperTwistContractLibrary::SerializeMemoryChronicleContinuityStateToJson(
			ChronicleContinuityState
		);
	TestFalse(TEXT("The serialized M2 JSON must not be empty."), Json.IsEmpty());
	TestTrue(
		TEXT("The serialized M2 JSON must expose the review-plan resume pack kind."),
		Json.Contains(TEXT("review-plan-resume"))
	);

	FHyperTwistMemoryChronicleContinuityState RoundTrippedState;
	TestTrue(
		TEXT("The M2 JSON must deserialize cleanly."),
		UHyperTwistContractLibrary::DeserializeMemoryChronicleContinuityStateFromJson(
			Json,
			RoundTrippedState
		)
	);
	TestTrue(
		TEXT("The round-tripped M2 state must remain structurally valid."),
		RoundTrippedState.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The round-tripped M2 state must preserve chronicle event counts."),
		RoundTrippedState.ChronicleEvents.Num(),
		ChronicleContinuityState.ChronicleEvents.Num()
	);
	TestEqual(
		TEXT("The round-tripped M2 state must preserve session group counts."),
		RoundTrippedState.SessionGroups.Num(),
		ChronicleContinuityState.SessionGroups.Num()
	);
	TestEqual(
		TEXT("The round-tripped M2 state must preserve resume pack counts."),
		RoundTrippedState.ResumePacks.Num(),
		ChronicleContinuityState.ResumePacks.Num()
	);
	TestEqual(
		TEXT("The round-tripped M2 state must preserve continuity guard counts."),
		RoundTrippedState.ContinuityGuards.Num(),
		ChronicleContinuityState.ContinuityGuards.Num()
	);
	TestEqual(
		TEXT("The round-tripped M2 state must preserve blocking-guard counts."),
		RoundTrippedState.BlockingGuardCount,
		ChronicleContinuityState.BlockingGuardCount
	);

	return true;
}

#endif
