// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
#include "HyperTwistCore/HyperTwistCoreLibrary.h"
#include "JsonObjectConverter.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistMelindaBound2CoreTestInternal
{
	template <typename TStruct>
	FString SerializeStructToJson(const TStruct& Value)
	{
		FString Json;
		FJsonObjectConverter::UStructToJsonObjectString(TStruct::StaticStruct(), &Value, Json, 0, 0);
		return Json;
	}

	FHyperTwistPuzzleState MakeStateWithPayload(
		const FHyperTwistPuzzleState& BaseState,
		const FHyperTwistMelinda2x2x2x2StateEncoding& Payload
	)
	{
		FHyperTwistPuzzleState State = BaseState;
		State.StateEncodingKind = EHyperTwistStateEncodingKind::FamilySpecific;
		State.StateEncoding.EncodingProfile = Payload.EncodingProfile;
		State.StateEncoding.PayloadJson = SerializeStructToJson(Payload);
		State.OrientationFrame.Reference = Payload.FrameProfile;
		State.bIsSolved = UHyperTwistCoreLibrary::IsSolved(State);
		return State;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMelindaBound2ValiditySurfaceTest,
	"HyperTwist.CleanRoom.HactarCE.Bound2.ValiditySurface",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMelindaBound2ValiditySurfaceTest::RunTest(const FString& Parameters)
{
	const FHyperTwistPuzzleState SolvedState = UHyperTwistContractLibrary::MakeSampleHyperPuzzleState();
	const FHyperTwistPuzzleStateValidationResult SolvedValidation = UHyperTwistCoreLibrary::ValidatePuzzleState(SolvedState);

	TestTrue(TEXT("Solved sample state must stay supported."), SolvedValidation.bStateSupported);
	TestTrue(TEXT("Solved sample state must stay structurally valid."), SolvedValidation.bStructureValid);
	TestTrue(TEXT("Solved sample state must have even permutation parity."), SolvedValidation.bPermutationParityEven);
	TestTrue(TEXT("Solved sample state must satisfy handedness/orientation parity compatibility."), SolvedValidation.bOrientationParityCompatible);
	TestTrue(TEXT("Solved sample state must conserve twist."), SolvedValidation.bTwistConserved);
	TestTrue(TEXT("Solved sample state must be solvable."), SolvedValidation.bIsSolvable);
	TestEqual(TEXT("Solved sample state must have zero twist balance."), SolvedValidation.TwistBalance, 0);

	FHyperTwistMelinda2x2x2x2StateEncoding SolvedPayload;
	TestTrue(
		TEXT("Solved sample payload must deserialize."),
		FJsonObjectConverter::JsonObjectStringToUStruct(SolvedState.StateEncoding.PayloadJson, &SolvedPayload, 0, 0)
	);

	FHyperTwistMelinda2x2x2x2StateEncoding OddPermutationPayload = SolvedPayload;
	Swap(OddPermutationPayload.PositionToPiece[0], OddPermutationPayload.PositionToPiece[1]);
	const FHyperTwistPuzzleStateValidationResult OddPermutationValidation =
		UHyperTwistCoreLibrary::ValidatePuzzleState(
			HyperTwistMelindaBound2CoreTestInternal::MakeStateWithPayload(SolvedState, OddPermutationPayload)
		);

	TestFalse(TEXT("A single swap must fail even-permutation validation."), OddPermutationValidation.bPermutationParityEven);
	TestTrue(
		TEXT("Odd permutation validation must report the parity failure."),
		OddPermutationValidation.Errors.Contains(TEXT("melinda-permutation-parity-odd"))
	);
	TestFalse(TEXT("Odd permutation state must not be solvable."), OddPermutationValidation.bIsSolvable);

	FHyperTwistMelinda2x2x2x2StateEncoding OrientationMismatchPayload = SolvedPayload;
	OrientationMismatchPayload.PieceOrientation[0] = 1;
	const FHyperTwistPuzzleStateValidationResult OrientationMismatchValidation =
		UHyperTwistCoreLibrary::ValidatePuzzleState(
			HyperTwistMelindaBound2CoreTestInternal::MakeStateWithPayload(SolvedState, OrientationMismatchPayload)
		);

	TestTrue(TEXT("The mismatch case should keep even permutation parity."), OrientationMismatchValidation.bPermutationParityEven);
	TestFalse(
		TEXT("The mismatch case must fail handedness/orientation parity compatibility."),
		OrientationMismatchValidation.bOrientationParityCompatible
	);
	TestTrue(
		TEXT("The mismatch case must report the handedness/orientation parity failure."),
		OrientationMismatchValidation.Errors.Contains(TEXT("melinda-orientation-parity-mismatch"))
	);
	TestFalse(TEXT("The mismatch case must not be solvable."), OrientationMismatchValidation.bIsSolvable);

	FHyperTwistMelinda2x2x2x2StateEncoding TwistMismatchPayload = SolvedPayload;
	TwistMismatchPayload.PieceOrientation[0] = 3;
	const FHyperTwistPuzzleStateValidationResult TwistMismatchValidation =
		UHyperTwistCoreLibrary::ValidatePuzzleState(
			HyperTwistMelindaBound2CoreTestInternal::MakeStateWithPayload(SolvedState, TwistMismatchPayload)
		);

	TestTrue(TEXT("The twist mismatch case should keep even permutation parity."), TwistMismatchValidation.bPermutationParityEven);
	TestTrue(
		TEXT("The twist mismatch case should keep handedness/orientation parity compatibility."),
		TwistMismatchValidation.bOrientationParityCompatible
	);
	TestFalse(TEXT("The twist mismatch case must fail twist conservation."), TwistMismatchValidation.bTwistConserved);
	TestTrue(
		TEXT("The twist mismatch case must report twist imbalance."),
		TwistMismatchValidation.Errors.Contains(TEXT("melinda-twist-balance-mismatch"))
	);
	TestEqual(TEXT("The twist mismatch case must report a +1 twist balance."), TwistMismatchValidation.TwistBalance, 1);
	TestFalse(TEXT("The twist mismatch case must not be solvable."), TwistMismatchValidation.bIsSolvable);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistMelindaBound2RandomStateGenerationTest,
	"HyperTwist.CleanRoom.HactarCE.Bound2.RandomStateGeneration",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistMelindaBound2RandomStateGenerationTest::RunTest(const FString& Parameters)
{
	const FHyperTwistPuzzleDefinitionRef Definition = UHyperTwistContractLibrary::MakeSampleHyperPuzzleDefinition();
	const FHyperTwistRandomStateGenerationResult FirstResult =
		UHyperTwistCoreLibrary::GenerateRandomPuzzleState(Definition, 1337);

	TestTrue(TEXT("Seeded Melinda generation must succeed."), FirstResult.bGenerated);
	TestEqual(TEXT("Bound 2 generation should remain single-pass."), FirstResult.AttemptsUsed, 1);
	TestTrue(TEXT("Generated state must remain structurally valid."), FirstResult.State.IsStructurallyValid());
	TestEqual(
		TEXT("Generated state must keep the Melinda state profile."),
		FirstResult.State.StateEncoding.EncodingProfile,
		FString(TEXT("melinda-2x2x2x2-state-v1"))
	);
	TestTrue(TEXT("Generated validation must report solvability."), FirstResult.Validation.bIsSolvable);
	TestTrue(TEXT("Generated validation must preserve even permutation parity."), FirstResult.Validation.bPermutationParityEven);
	TestTrue(
		TEXT("Generated validation must preserve handedness/orientation compatibility."),
		FirstResult.Validation.bOrientationParityCompatible
	);
	TestTrue(TEXT("Generated validation must preserve twist conservation."), FirstResult.Validation.bTwistConserved);

	const FHyperTwistPuzzleStateValidationResult Revalidated =
		UHyperTwistCoreLibrary::ValidatePuzzleState(FirstResult.State);
	TestTrue(TEXT("Generated state must revalidate through the owned validity surface."), Revalidated.bIsSolvable);

	const FString FirstSerializedState = UHyperTwistContractLibrary::SerializePuzzleStateToJson(FirstResult.State);
	const FHyperTwistRandomStateGenerationResult RepeatedResult =
		UHyperTwistCoreLibrary::GenerateRandomPuzzleState(Definition, 1337);
	const FString RepeatedSerializedState = UHyperTwistContractLibrary::SerializePuzzleStateToJson(RepeatedResult.State);

	TestTrue(TEXT("Repeated seeded generation must also succeed."), RepeatedResult.bGenerated);
	TestEqual(
		TEXT("Seeded Melinda generation must be deterministic."),
		RepeatedSerializedState,
		FirstSerializedState
	);

	const FHyperTwistRandomStateGenerationResult DifferentSeedResult =
		UHyperTwistCoreLibrary::GenerateRandomPuzzleState(Definition, 1338);
	TestTrue(TEXT("A different seed must still generate a solvable state."), DifferentSeedResult.bGenerated);
	TestTrue(TEXT("Different-seed state must still validate."), DifferentSeedResult.Validation.bIsSolvable);

	for (int32 Seed = 0; Seed < 32; ++Seed)
	{
		const FHyperTwistRandomStateGenerationResult LoopResult =
			UHyperTwistCoreLibrary::GenerateRandomPuzzleState(Definition, Seed);
		TestTrue(
			FString::Printf(TEXT("Seed %d must generate a solvable Melinda state."), Seed),
			LoopResult.bGenerated
		);
		TestTrue(
			FString::Printf(TEXT("Seed %d must revalidate through the owned validity surface."), Seed),
			LoopResult.Validation.bIsSolvable
		);
	}

	return true;
}

#endif // WITH_AUTOMATION_TESTS
