#include "Misc/AutomationTest.h"

#include "HyperTwistSimulation/HyperTwistMagic120CellRuntimeLibrary.h"

#if WITH_AUTOMATION_TESTS

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMagic120CellStateProjectionContractTest,
	"HyperTwist.Permissive.Magic120Cell.StateProjectionContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMagic120CellStateProjectionContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	FString TableError;
	TestTrue(
		TEXT("Magic120Cell exact permutation table must decode and validate."),
		UHyperTwistMagic120CellRuntimeLibrary::IsPermutationTableReady(TableError));
	if (!TableError.IsEmpty())
	{
		AddError(TableError);
	}

	const FHyperTwistMagic120CellRuntimeState SolvedState =
		UHyperTwistMagic120CellRuntimeLibrary::BuildSolvedState();
	TestTrue(
		TEXT("Magic120Cell solved state must be structurally valid."),
		SolvedState.IsStructurallyValid());
	TestTrue(TEXT("Magic120Cell fresh state must be solved."), SolvedState.bIsSolved);
	TestEqual(
		TEXT("Magic120Cell must own all 120 x 63 facelet slots."),
		SolvedState.StickerColorIndices.Num(),
		7560);

	const FHyperTwistMagic120CellProjectionBuildResult Projection =
		UHyperTwistMagic120CellRuntimeLibrary::BuildProjection(SolvedState);
	TestTrue(
		TEXT("Magic120Cell solved projection must be exact."),
		Projection.bProjected && Projection.bExactProjection);
	TestTrue(
		TEXT("Magic120Cell solved projection must be structurally valid."),
		Projection.Projection.IsStructurallyValid());
	TestEqual(
		TEXT("Magic120Cell projection must retain all 120 cells."),
		Projection.Projection.Cells.Num(),
		120);

	int32 ProjectedStickerCount = 0;
	for (const FHyperTwistMagic120CellProjectedCell& Cell :
		 Projection.Projection.Cells)
	{
		ProjectedStickerCount += Cell.StickerColorIndices.Num();
		TestTrue(TEXT("Every solved projected cell must be solved."), Cell.bCellSolved);
	}
	TestEqual(
		TEXT("Magic120Cell projection must retain all 7,560 facelets."),
		ProjectedStickerCount,
		7560);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMagic120CellPermutationCoverageContractTest,
	"HyperTwist.Permissive.Magic120Cell.PermutationCoverageContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMagic120CellPermutationCoverageContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	const FHyperTwistMagic120CellRuntimeState SolvedState =
		UHyperTwistMagic120CellRuntimeLibrary::BuildSolvedState();
	for (int32 CellIndex = 0; CellIndex < 120; ++CellIndex)
	{
		for (int32 StickerIndex = 1; StickerIndex < 63; ++StickerIndex)
		{
			FHyperTwistMagic120CellTurnRequest Request;
			Request.CellIndex = CellIndex;
			Request.StickerIndex = StickerIndex;
			const FHyperTwistMagic120CellTurnResult Turn =
				UHyperTwistMagic120CellRuntimeLibrary::ApplyTurn(
					SolvedState,
					Request);
			if (!Turn.bApplied
				|| !Turn.bExactStateUpdate
				|| Turn.State.bIsSolved
				|| (Turn.MovedStickerCount != 190
					&& Turn.MovedStickerCount != 192))
			{
				AddError(FString::Printf(
					TEXT("Exact move failed for cell %d sticker %d."),
					CellIndex,
					StickerIndex));
				return false;
			}

			Request.bInverse = true;
			const FHyperTwistMagic120CellTurnResult Inverse =
				UHyperTwistMagic120CellRuntimeLibrary::ApplyTurn(
					Turn.State,
					Request);
			if (!Inverse.bApplied
				|| !Inverse.bExactStateUpdate
				|| !Inverse.State.bIsSolved)
			{
				AddError(FString::Printf(
					TEXT("Exact inverse failed for cell %d sticker %d."),
					CellIndex,
					StickerIndex));
				return false;
			}
		}
	}
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMagic120CellTurnOrderContractTest,
	"HyperTwist.Permissive.Magic120Cell.TurnOrderContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMagic120CellTurnOrderContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	const int32 RepresentativeStickers[] = {1, 13, 43};
	const int32 ExpectedOrders[] = {5, 2, 3};
	for (int32 RepresentativeIndex = 0;
		 RepresentativeIndex < UE_ARRAY_COUNT(RepresentativeStickers);
		 ++RepresentativeIndex)
	{
		const int32 StickerIndex = RepresentativeStickers[RepresentativeIndex];
		const int32 TurnOrder =
			UHyperTwistMagic120CellRuntimeLibrary::GetTurnOrderForSticker(
				StickerIndex);
		TestEqual(
			TEXT("Magic120Cell axis family must expose its geometric turn order."),
			TurnOrder,
			ExpectedOrders[RepresentativeIndex]);

		FHyperTwistMagic120CellRuntimeState State =
			UHyperTwistMagic120CellRuntimeLibrary::BuildSolvedState();
		FHyperTwistMagic120CellTurnRequest Request;
		Request.CellIndex = 37;
		Request.StickerIndex = StickerIndex;
		for (int32 TurnIndex = 0; TurnIndex < TurnOrder; ++TurnIndex)
		{
			const FHyperTwistMagic120CellTurnResult Turn =
				UHyperTwistMagic120CellRuntimeLibrary::ApplyTurn(State, Request);
			TestTrue(
				TEXT("Magic120Cell geometric-order turn must remain exact."),
				Turn.bApplied && Turn.bExactStateUpdate);
			State = Turn.State;
		}
		TestTrue(
			TEXT("Magic120Cell geometric turn order must recover identity."),
			State.bIsSolved);
	}
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMagic120CellScramblePersistenceContractTest,
	"HyperTwist.Permissive.Magic120Cell.ScramblePersistenceContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMagic120CellScramblePersistenceContractTest::RunTest(
	const FString& Parameters
)
{
	static_cast<void>(Parameters);
	const FHyperTwistMagic120CellScrambleResult Scramble =
		UHyperTwistMagic120CellRuntimeLibrary::GenerateScramble(100, 120631);
	const FHyperTwistMagic120CellScrambleResult Repeat =
		UHyperTwistMagic120CellRuntimeLibrary::GenerateScramble(100, 120631);
	TestTrue(
		TEXT("Magic120Cell scramble must generate an exact state."),
		Scramble.bGenerated
			&& Scramble.bExactStateUpdate
			&& Scramble.State.IsStructurallyValid());
	TestFalse(
		TEXT("Magic120Cell scramble must leave solved identity."),
		Scramble.State.bIsSolved);
	TestEqual(
		TEXT("Magic120Cell scramble must retain every move."),
		Scramble.Moves.Num(),
		100);

	const FString Json =
		UHyperTwistMagic120CellRuntimeLibrary::SerializeRuntimeStateToJson(
			Scramble.State);
	TestEqual(
		TEXT("Equal seeds must produce identical Magic120Cell state."),
		Json,
		UHyperTwistMagic120CellRuntimeLibrary::SerializeRuntimeStateToJson(
			Repeat.State));

	FHyperTwistMagic120CellRuntimeState Restored;
	TestTrue(
		TEXT("Magic120Cell exact state must round-trip through JSON."),
		UHyperTwistMagic120CellRuntimeLibrary::TryDeserializeRuntimeStateFromJson(
			Json,
			Restored));
	TestEqual(
		TEXT("Restored Magic120Cell state must reserialize identically."),
		UHyperTwistMagic120CellRuntimeLibrary::SerializeRuntimeStateToJson(
			Restored),
		Json);

	const FHyperTwistMagic120CellProjectionBuildResult Projection =
		UHyperTwistMagic120CellRuntimeLibrary::BuildProjection(Restored);
	TestTrue(
		TEXT("Scrambled Magic120Cell state must retain an exact projection."),
		Projection.bProjected && Projection.bExactProjection);

	FHyperTwistMagic120CellRuntimeState Invalid = Restored;
	Invalid.StickerColorIndices[0] = 121;
	Invalid.bIsSolved = false;
	TestFalse(
		TEXT("Magic120Cell state must reject an invalid color index."),
		Invalid.IsStructurallyValid());
	return true;
}

#endif
