// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "HyperTwistTraining/HyperTwistTrainingControlPlaneLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingKnowledgeLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingPublicationLibrary.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistCubeDeskBound4PublicationEntitlementTestInternal
{
	FHyperTwistTrainingDeck MakeHypercubeDeck(const FString& DeckId)
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = DeckId;
		Deck.Title = TEXT("3x3x3x3 Fundamentals");
		Deck.DeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer};
		Deck.Tags = {TEXT("4d"), TEXT("hypercube"), TEXT("competitive")};

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("bound4-case-1");
		TrainingCase.PuzzleId = TEXT("3x3x3x3");
		TrainingCase.PromptLabel = TEXT("Bound4 Hypercube Case");
		TrainingCase.AllowedDeliveryModes = {EHyperTwistTrainingDeliveryMode::Timer};
		Deck.Cases.Add(TrainingCase);
		return Deck;
	}

	FHyperTwistTrainingLearnerProfile MakeLearnerProfile(
		const FString& UserId,
		const int32 TotalSessionCount,
		const int32 CompletedSessionCount
	)
	{
		FHyperTwistTrainingLearnerProfile Profile;
		Profile.UserId = UserId;
		Profile.ReferenceUtc = TEXT("2026-05-19T12:00:00Z");
		Profile.TotalSessionCount = TotalSessionCount;
		Profile.CompletedSessionCount = CompletedSessionCount;
		Profile.TotalAttemptCount = 24;
		Profile.TotalSuccessCount = 18;
		Profile.TotalFailureCount = 6;
		Profile.SuccessRate = 75.0f;
		Profile.ActiveTrainingDayStreak = 5;
		Profile.LongestObservedTrainingDayStreak = 8;
		Profile.PrimaryDeliveryMode = EHyperTwistTrainingDeliveryMode::Timer;
		Profile.FavoriteDeckId = TEXT("deck/hypercube-3333");
		Profile.FavoriteDeckSessionShare = 0.85f;
		Profile.LastTrainingAtUtc = TEXT("2026-05-19T11:58:00Z");
		return Profile;
	}

	FHyperTwistTrainingDeckHistorySummary MakeDeckHistorySummary(const FString& UserId, const FString& DeckId)
	{
		FHyperTwistTrainingDeckHistorySummary Summary;
		Summary.UserId = UserId;
		Summary.DeckId = DeckId;
		Summary.SessionCount = 4;
		Summary.CompletedSessionCount = 3;
		Summary.TotalAttemptCount = 24;
		Summary.SuccessCount = 18;
		Summary.FailureCount = 4;
		Summary.TimeoutCount = 2;
		Summary.BestTotalTimeMs = 1890;
		Summary.BestRawSolveTimeMs = 1710;
		Summary.AverageTotalTimeMs = 2330;
		Summary.AverageRawSolveTimeMs = 2140;
		Summary.LastSessionId = TEXT("bound4-session");
		Summary.LastCompletedAtUtc = TEXT("2026-05-19T11:58:00Z");
		return Summary;
	}

	FHyperTwistTrainingPersonalBestSummary MakePersonalBestSummary(const FString& UserId, const FString& DeckId)
	{
		FHyperTwistTrainingPersonalBestSummary Summary;
		Summary.UserId = UserId;
		Summary.DeckId = DeckId;
		Summary.ReferenceUtc = TEXT("2026-05-19T11:58:00Z");
		Summary.BestSingleSessionId = TEXT("bound4-session");
		Summary.BestSingleTotalTimeMs = 1890;
		Summary.BestSingleRawSolveTimeMs = 1710;
		Summary.BestAverageSessionId = TEXT("bound4-session");
		Summary.BestSessionAverageTotalTimeMs = 2050;
		Summary.BestSessionAverageRawSolveTimeMs = 1930;
		Summary.LastPersonalBestAtUtc = TEXT("2026-05-19T11:58:00Z");
		Summary.bHasTimedPersonalBest = true;
		return Summary;
	}

	bool ResolveControlPlaneContracts(
		FHyperTwistTrainingFeatureGovernanceContract& OutFeatureGovernanceContract,
		FHyperTwistTrainingEarlyAccessRolloutContract& OutEarlyAccessContract
	)
	{
		const FHyperTwistTrainingControlPlaneReferenceBundle ControlPlaneBundle =
			UHyperTwistTrainingControlPlaneLibrary::BuildBundledControlPlaneReferenceBundle();
		return UHyperTwistTrainingControlPlaneLibrary::TryGetFeatureGovernanceContractById(
				ControlPlaneBundle,
				ControlPlaneBundle.PrimaryFeatureGovernanceContractId,
				OutFeatureGovernanceContract
			)
			&& UHyperTwistTrainingControlPlaneLibrary::TryGetEarlyAccessRolloutContractById(
				ControlPlaneBundle,
				ControlPlaneBundle.PrimaryEarlyAccessRolloutContractId,
				OutEarlyAccessContract
			);
	}

	const FHyperTwistTrainingLeaderboardEntry* FindEntryByEventId(
		const FHyperTwistTrainingLeaderboardBundle& LeaderboardBundle,
		const FString& EventId
	)
	{
		for (const FHyperTwistTrainingLeaderboardEntry& Entry : LeaderboardBundle.Entries)
		{
			if (Entry.EventId == EventId)
			{
				return &Entry;
			}
		}

		return nullptr;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistCubeDeskBound4PublicationBundleTest,
	"HyperTwist.CleanRoom.CubeDesk.Bound4.PublicationBundle",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistCubeDeskBound4PublicationBundleTest::RunTest(const FString& Parameters)
{
	FHyperTwistTrainingFeatureGovernanceContract FeatureGovernanceContract;
	FHyperTwistTrainingEarlyAccessRolloutContract EarlyAccessContract;
	TestTrue(
		TEXT("Control-plane publication contracts must resolve from the bundled PostHog packet."),
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::ResolveControlPlaneContracts(
			FeatureGovernanceContract,
			EarlyAccessContract
		)
	);

	const FHyperTwistTrainingDeck Deck =
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeHypercubeDeck(
			TEXT("deck/hypercube-3333")
		);
	const FHyperTwistTrainingKnowledgeReferenceBundle KnowledgeBundle =
		UHyperTwistTrainingKnowledgeLibrary::MakeHypercubingKnowledgeReferenceBundle();
	const FHyperTwistTrainingPublicationBundle Bundle =
		UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
			HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeLearnerProfile(
				TEXT("bound4-user"),
				4,
				3
			),
			HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeDeckHistorySummary(
				TEXT("bound4-user"),
				Deck.DeckId
			),
			HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakePersonalBestSummary(
				TEXT("bound4-user"),
				Deck.DeckId
			),
			Deck,
			KnowledgeBundle,
			FeatureGovernanceContract,
			EarlyAccessContract
		);

	TestTrue(TEXT("The publication bundle must resolve as structurally valid."), Bundle.IsStructurallyValid());
	TestTrue(TEXT("The entitlement gate must be visible for active learners."), Bundle.EntitlementGate.bVisible);
	TestTrue(TEXT("The entitlement gate must resolve to entitled when local publication exists."), Bundle.EntitlementGate.bEntitled);
	TestEqual(
		TEXT("The publication bundle must promote to the published visibility state."),
		Bundle.ProfilePublication.Visibility,
		EHyperTwistTrainingPublicationVisibility::Published
	);
	TestEqual(
		TEXT("The publication bundle must retain the active deck title."),
		Bundle.ProfilePublication.DeckTitle,
		Deck.Title
	);
	TestEqual(
		TEXT("The publication bundle must retain the active user as its display name."),
		Bundle.ProfilePublication.DisplayName,
		TEXT("bound4-user")
	);
	TestTrue(
		TEXT("The publication bundle must expose a formatted highlight value."),
		!Bundle.ProfilePublication.HighlightPrimaryValue.IsEmpty()
	);
	TestTrue(
		TEXT("The publication bundle must report matched leaderboard events for the hypercube deck."),
		Bundle.bHasMatchedLeaderboardEvents
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistCubeDeskBound4EntitlementGateTest,
	"HyperTwist.CleanRoom.CubeDesk.Bound4.EntitlementGate",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistCubeDeskBound4EntitlementGateTest::RunTest(const FString& Parameters)
{
	FHyperTwistTrainingFeatureGovernanceContract FeatureGovernanceContract;
	FHyperTwistTrainingEarlyAccessRolloutContract EarlyAccessContract;
	TestTrue(
		TEXT("Control-plane publication contracts must resolve for entitlement gating."),
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::ResolveControlPlaneContracts(
			FeatureGovernanceContract,
			EarlyAccessContract
		)
	);

	const FHyperTwistTrainingDeck Deck =
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeHypercubeDeck(
			TEXT("deck/hypercube-preview")
		);
	FHyperTwistTrainingDeckHistorySummary DeckHistorySummary =
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeDeckHistorySummary(
			TEXT("bound4-preview-user"),
			Deck.DeckId
		);
	DeckHistorySummary.CompletedSessionCount = 0;
	FHyperTwistTrainingPersonalBestSummary PersonalBestSummary =
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakePersonalBestSummary(
			TEXT("bound4-preview-user"),
			Deck.DeckId
		);
	PersonalBestSummary.bHasTimedPersonalBest = false;
	PersonalBestSummary.BestSingleTotalTimeMs = 0;
	PersonalBestSummary.BestSessionAverageTotalTimeMs = 0;

	const FHyperTwistTrainingPublicationBundle Bundle =
		UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
			HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeLearnerProfile(
				TEXT("bound4-preview-user"),
				2,
				0
			),
			DeckHistorySummary,
			PersonalBestSummary,
			Deck,
			UHyperTwistTrainingKnowledgeLibrary::MakeHypercubingKnowledgeReferenceBundle(),
			FeatureGovernanceContract,
			EarlyAccessContract
		);

	TestTrue(TEXT("The preview publication bundle must still be structurally valid."), Bundle.IsStructurallyValid());
	TestTrue(TEXT("Preview publication must still be visible when the lifecycle stage is active."), Bundle.EntitlementGate.bVisible);
	TestFalse(TEXT("Preview publication must not be entitled without earned local publication data."), Bundle.EntitlementGate.bEntitled);
	TestEqual(
		TEXT("Preview publication must remain in the preview visibility state."),
		Bundle.ProfilePublication.Visibility,
		EHyperTwistTrainingPublicationVisibility::Preview
	);
	TestEqual(
		TEXT("The entitlement visibility label must describe preview posture explicitly."),
		Bundle.EntitlementGate.VisibilityLabel,
		TEXT("preview")
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistCubeDeskBound4LeaderboardProjectionTest,
	"HyperTwist.CleanRoom.CubeDesk.Bound4.LeaderboardProjection",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistCubeDeskBound4LeaderboardProjectionTest::RunTest(const FString& Parameters)
{
	FHyperTwistTrainingFeatureGovernanceContract FeatureGovernanceContract;
	FHyperTwistTrainingEarlyAccessRolloutContract EarlyAccessContract;
	TestTrue(
		TEXT("Control-plane publication contracts must resolve for leaderboard projection."),
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::ResolveControlPlaneContracts(
			FeatureGovernanceContract,
			EarlyAccessContract
		)
	);

	const FHyperTwistTrainingDeck Deck =
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeHypercubeDeck(
			TEXT("deck/hypercube-leaderboard")
		);
	const FHyperTwistTrainingPublicationBundle Bundle =
		UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
			HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeLearnerProfile(
				TEXT("bound4-leaderboard-user"),
				5,
				4
			),
			HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakeDeckHistorySummary(
				TEXT("bound4-leaderboard-user"),
				Deck.DeckId
			),
			HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::MakePersonalBestSummary(
				TEXT("bound4-leaderboard-user"),
				Deck.DeckId
			),
			Deck,
			UHyperTwistTrainingKnowledgeLibrary::MakeHypercubingKnowledgeReferenceBundle(),
			FeatureGovernanceContract,
			EarlyAccessContract
		);

	TestTrue(TEXT("The leaderboard bundle must resolve as structurally valid."), Bundle.Leaderboard.IsStructurallyValid());
	TestEqual(
		TEXT("The hypercube deck must match exactly two bundled leaderboard events."),
		Bundle.Leaderboard.MatchedEventCount,
		2
	);
	TestEqual(
		TEXT("The leaderboard projection must materialize a single and an Ao5 row."),
		Bundle.Leaderboard.Entries.Num(),
		2
	);
	TestTrue(
		TEXT("The leaderboard projection must retain the default rank column."),
		Bundle.Leaderboard.ColumnOrder.Contains(TEXT("rank"))
	);
	const FHyperTwistTrainingLeaderboardEntry* SingleEntry =
		HyperTwistCubeDeskBound4PublicationEntitlementTestInternal::FindEntryByEventId(
			Bundle.Leaderboard,
			TEXT("event/3x3x3x3/single")
		);
	TestNotNull(TEXT("The single-event leaderboard row must exist."), SingleEntry);
	if (SingleEntry == nullptr)
	{
		return false;
	}

	TestEqual(
		TEXT("The single-event leaderboard row must retain the preferred program badge."),
		SingleEntry->ProgramLabel,
		TEXT("HSC")
	);
	TestTrue(
		TEXT("The single-event leaderboard row must expose a formatted metric label."),
		!SingleEntry->MetricPrimaryValue.IsEmpty()
	);
	TestTrue(
		TEXT("Published leaderboard projections must expose published entries."),
		Bundle.Leaderboard.bHasPublishedEntries
	);
	return true;
}

#endif
