#include "Misc/AutomationTest.h"

#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
#include "HyperTwistCore/HyperTwistCoreLibrary.h"
#include "HyperTwistSimulation/HyperTwistMelindaProjectionLibrary.h"
#include "HyperTwistSimulation/HyperTwistSimulationLibrary.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistMelindaPhase6AContractTestInternal
{
	FString SerializeState(const FHyperTwistPuzzleState& State)
	{
		return UHyperTwistContractLibrary::SerializePuzzleStateToJson(State);
	}

	bool ApplyExactTransform(
		FAutomationTestBase& Test,
		const FHyperTwistPuzzleState& State,
		const FHyperTwistTransformation& Transformation,
		FHyperTwistPuzzleState& OutState
	)
	{
		const FHyperTwistApplyTransformationResult Applied =
			UHyperTwistCoreLibrary::ApplyTransformation(State, Transformation);
		Test.TestTrue(TEXT("Melinda Phase 6A transforms must apply."), Applied.bApplied);
		Test.TestTrue(
			TEXT("Melinda Phase 6A transforms must produce exact state updates."),
			Applied.bExactStateUpdate
		);
		if (!Applied.bApplied || !Applied.bExactStateUpdate)
		{
			return false;
		}

		const FHyperTwistPuzzleStateValidationResult Validation =
			UHyperTwistCoreLibrary::ValidatePuzzleState(Applied.State);
		Test.TestTrue(
			TEXT("Applied Melinda Phase 6A states must remain solvable."),
			Validation.bIsSolvable
		);
		OutState = Applied.State;
		return Validation.bIsSolvable;
	}

	EHyperTwistMelindaTurnDirection InvertDirection(
		const EHyperTwistMelindaTurnDirection Direction
	)
	{
		return Direction == EHyperTwistMelindaTurnDirection::Clockwise
			? EHyperTwistMelindaTurnDirection::CounterClockwise
			: EHyperTwistMelindaTurnDirection::Clockwise;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMelindaPhase6ACellTurnContractTest,
	"HyperTwist.CleanRoom.HactarCE.Phase6A.CellTurnContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMelindaPhase6ACellTurnContractTest::RunTest(const FString& Parameters)
{
	const FHyperTwistPuzzleDefinitionRef Definition =
		UHyperTwistContractLibrary::MakeSampleHyperPuzzleDefinition();
	const FHyperTwistPuzzleState SolvedState =
		UHyperTwistContractLibrary::MakeSampleHyperPuzzleState();
	const FString SerializedSolvedState =
		HyperTwistMelindaPhase6AContractTestInternal::SerializeState(SolvedState);

	static const EHyperTwistMelindaCell Cells[] = {
		EHyperTwistMelindaCell::Left,
		EHyperTwistMelindaCell::Right,
		EHyperTwistMelindaCell::Back,
		EHyperTwistMelindaCell::Front,
		EHyperTwistMelindaCell::Down,
		EHyperTwistMelindaCell::Up,
		EHyperTwistMelindaCell::Inner,
		EHyperTwistMelindaCell::Outer
	};
	static const EHyperTwistMelindaTurnDirection Directions[] = {
		EHyperTwistMelindaTurnDirection::Clockwise,
		EHyperTwistMelindaTurnDirection::CounterClockwise
	};

	for (const EHyperTwistMelindaCell Cell : Cells)
	{
		const TArray<EHyperTwistMelindaCellAxis> AvailableAxes =
			UHyperTwistMelindaProjectionLibrary::GetAvailableAxesForCell(Cell);
		TestEqual(TEXT("Each Phase 6A cell should expose exactly three twist axes."), AvailableAxes.Num(), 3);

		for (const EHyperTwistMelindaCellAxis Axis : AvailableAxes)
		{
			for (const EHyperTwistMelindaTurnDirection Direction : Directions)
			{
				FHyperTwistMelindaCellTurnRequest Request;
				Request.Cell = Cell;
				Request.RotationAxis = Axis;
				Request.Direction = Direction;

				const FHyperTwistMelindaCellTurnTransformBuildResult BuildResult =
					UHyperTwistMelindaProjectionLibrary::BuildCellTurnTransformation(
						Definition,
						Request
					);
				TestTrue(TEXT("Each Phase 6A cell turn should build."), BuildResult.bBuilt);
				TestTrue(TEXT("Each Phase 6A cell turn should remain exact."), BuildResult.bExactTransform);
				TestTrue(
					TEXT("Each Phase 6A cell turn must produce structural transform data."),
					BuildResult.Transformation.IsStructurallyValid()
				);

				FHyperTwistPuzzleState OnceTurnedState;
				if (!HyperTwistMelindaPhase6AContractTestInternal::ApplyExactTransform(
						*this,
						SolvedState,
						BuildResult.Transformation,
						OnceTurnedState
					))
				{
					return false;
				}

				TestFalse(
					TEXT("Any Phase 6A cell turn should disturb the solved state."),
					UHyperTwistCoreLibrary::IsSolved(OnceTurnedState)
				);

				FHyperTwistMelindaCellTurnRequest InverseRequest = Request;
				InverseRequest.Direction =
					HyperTwistMelindaPhase6AContractTestInternal::InvertDirection(Direction);
				const FHyperTwistMelindaCellTurnTransformBuildResult InverseBuildResult =
					UHyperTwistMelindaProjectionLibrary::BuildCellTurnTransformation(
						Definition,
						InverseRequest
					);
				TestTrue(TEXT("Inverse Phase 6A turns should build."), InverseBuildResult.bBuilt);
				TestTrue(TEXT("Inverse Phase 6A turns should remain exact."), InverseBuildResult.bExactTransform);

				FHyperTwistPuzzleState RecoveredState;
				if (!HyperTwistMelindaPhase6AContractTestInternal::ApplyExactTransform(
						*this,
						OnceTurnedState,
						InverseBuildResult.Transformation,
						RecoveredState
					))
				{
					return false;
				}

				TestEqual(
					TEXT("A turn followed by its inverse must recover the solved Melinda state."),
					HyperTwistMelindaPhase6AContractTestInternal::SerializeState(RecoveredState),
					SerializedSolvedState
				);

				FHyperTwistPuzzleState QuadTurnState = SolvedState;
				for (int32 TurnIndex = 0; TurnIndex < 4; ++TurnIndex)
				{
					FHyperTwistPuzzleState NextState;
					if (!HyperTwistMelindaPhase6AContractTestInternal::ApplyExactTransform(
							*this,
							QuadTurnState,
							BuildResult.Transformation,
							NextState
						))
					{
						return false;
					}

					QuadTurnState = NextState;
				}

				TestEqual(
					TEXT("Four quarter turns on one Phase 6A cell axis must return to identity."),
					HyperTwistMelindaPhase6AContractTestInternal::SerializeState(QuadTurnState),
					SerializedSolvedState
				);
			}
		}
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMelindaPhase6ACellFirstProjectionContractTest,
	"HyperTwist.CleanRoom.HactarCE.Phase6A.CellFirstProjectionContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMelindaPhase6ACellFirstProjectionContractTest::RunTest(const FString& Parameters)
{
	const FHyperTwistPuzzleState SolvedState =
		UHyperTwistContractLibrary::MakeSampleHyperPuzzleState();
	const FHyperTwistMelindaCellFirstProjectionBuildResult SolvedProjectionResult =
		UHyperTwistMelindaProjectionLibrary::BuildCellFirstProjection(SolvedState, 120.0f);

	TestTrue(TEXT("The solved Melinda state must project in Phase 6A."), SolvedProjectionResult.bProjected);
	TestTrue(TEXT("The solved Melinda projection must remain exact."), SolvedProjectionResult.bExactProjection);
	TestTrue(
		TEXT("The solved Melinda projection must stay structurally valid."),
		SolvedProjectionResult.Projection.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The Phase 6A projection profile must stay stable."),
		SolvedProjectionResult.Projection.ProjectionProfile,
		FString(TEXT("melinda-2x2x2x2-cell-first-projection-v1"))
	);

	TMap<int32, int32> PositionAppearanceCount;
	TSet<FString> CellLabels;
	for (const FHyperTwistMelindaProjectedCell& CellProjection :
		 SolvedProjectionResult.Projection.Cells)
	{
		CellLabels.Add(CellProjection.CellLabel);
		TestEqual(TEXT("Each projected Phase 6A cell should expose eight cubies."), CellProjection.Cubies.Num(), 8);
		TestEqual(
			TEXT("Each projected Phase 6A cell should expose exactly three available axes."),
			CellProjection.AvailableAxes.Num(),
			3
		);
		TestFalse(
			TEXT("The fixed axis must not be listed as an available Phase 6A turn axis."),
			CellProjection.AvailableAxes.Contains(CellProjection.FixedAxis)
		);
		for (const EHyperTwistMelindaCellAxis Axis : CellProjection.AvailableAxes)
		{
			TestTrue(
				TEXT("Every listed Phase 6A axis must be turnable for its cell."),
				UHyperTwistMelindaProjectionLibrary::IsAxisAvailableForCell(CellProjection.Cell, Axis)
			);
		}

		for (const FHyperTwistMelindaProjectedCubie& CubieProjection : CellProjection.Cubies)
		{
			PositionAppearanceCount.FindOrAdd(CubieProjection.PositionIndex)++;
			TestEqual(
				TEXT("Each projected cubie should expose three visible faces in the cell-first shell."),
				CubieProjection.Faces.Num(),
				3
			);
		}
	}

	TestEqual(TEXT("Phase 6A should expose all eight named cells."), CellLabels.Num(), 8);
	for (int32 PositionIndex = 0; PositionIndex < 16; ++PositionIndex)
	{
		TestEqual(
			TEXT("Each 4D cubie should appear in four projected cells."),
			PositionAppearanceCount.FindRef(PositionIndex),
			4
		);
	}

	const FHyperTwistRandomStateGenerationResult RandomStateResult =
		UHyperTwistCoreLibrary::GenerateRandomPuzzleState(
			UHyperTwistContractLibrary::MakeSampleHyperPuzzleDefinition(),
			62026
		);
	TestTrue(TEXT("Phase 6A random-state coverage must begin with an exact Melinda state."), RandomStateResult.bGenerated);
	TestTrue(TEXT("The generated Phase 6A random state must remain solvable."), RandomStateResult.Validation.bIsSolvable);

	const FHyperTwistMelindaCellFirstProjectionBuildResult RandomProjectionResult =
		UHyperTwistMelindaProjectionLibrary::BuildCellFirstProjection(
			RandomStateResult.State,
			120.0f
		);
	TestTrue(TEXT("Random Melinda states must project in Phase 6A."), RandomProjectionResult.bProjected);
	TestTrue(TEXT("Random Melinda projections must remain exact."), RandomProjectionResult.bExactProjection);
	TestTrue(
		TEXT("Random Melinda projections must remain structurally valid."),
		RandomProjectionResult.Projection.IsStructurallyValid()
	);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMelindaPhase6ASceneContextContractTest,
	"HyperTwist.CleanRoom.HactarCE.Phase6A.SceneContextContract",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMelindaPhase6ASceneContextContractTest::RunTest(const FString& Parameters)
{
	const FHyperTwistSimulationSceneContext SceneContext =
		UHyperTwistSimulationLibrary::MakeMelindaCellFirstSceneContext();

	TestTrue(TEXT("The Phase 6A scene context should be structurally valid."), SceneContext.IsStructurallyValid());
	TestTrue(TEXT("The Phase 6A scene context should be renderable."), UHyperTwistSimulationLibrary::CanRenderSceneContext(SceneContext));
	TestEqual(
		TEXT("The Phase 6A scene context should use the cell-first projection profile."),
		SceneContext.ProjectionSettings.ProjectionProfile,
		FString(TEXT("melinda-2x2x2x2-cell-first-projection-v1"))
	);
	TestEqual(
		TEXT("The Phase 6A scene context should advertise the owned procedural runtime."),
		SceneContext.RenderStateProfile,
		FString(TEXT("melinda-cell-first-procedural-runtime-v1"))
	);
	return true;
}

#endif
