// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "Engine/GameInstance.h"
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "UObject/Package.h"
#include "UObject/UnrealType.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistCubeDeskBound1TrainingTimerTestInternal
{
	template <typename PropertyType>
	PropertyType* FindTypedProperty(UObject* Object, const FName PropertyName)
	{
		return Object != nullptr
			? CastField<PropertyType>(Object->GetClass()->FindPropertyByName(PropertyName))
			: nullptr;
	}

	template <typename StructType>
	bool SetStructProperty(UObject* Object, const FName PropertyName, const StructType& Value)
	{
		if (FStructProperty* Property = FindTypedProperty<FStructProperty>(Object, PropertyName))
		{
			if (Property->Struct == StructType::StaticStruct())
			{
				*Property->ContainerPtrToValuePtr<StructType>(Object) = Value;
				return true;
			}
		}

		return false;
	}

	bool SetBoolProperty(UObject* Object, const FName PropertyName, const bool bValue)
	{
		if (FBoolProperty* Property = FindTypedProperty<FBoolProperty>(Object, PropertyName))
		{
			Property->SetPropertyValue_InContainer(Object, bValue);
			return true;
		}

		return false;
	}

	FHyperTwistTrainingDeck FindSampleMethodDrillDeck()
	{
		const FHyperTwistContentPack SamplePack = UHyperTwistTrainingLibrary::MakeSampleClassicContentPack();
		for (const FHyperTwistTrainingTrack& Track : SamplePack.Tracks)
		{
			for (const FHyperTwistTrainingDeck& Deck : Track.Decks)
			{
				if (!Deck.IsStructurallyValid())
				{
					continue;
				}

				const FHyperTwistTrainingMethodDrillDefinition DrillDefinition =
					UHyperTwistTrainingLibrary::BuildMethodDrillDefinitionFromDeck(
						Deck,
						EHyperTwistTrainingDrillModeKind::Recognition,
						EHyperTwistTrainingDrillVisibilityPolicy::PromptOnly,
						true,
						EHyperTwistTrainingDrillEntrySource::Catalog
					);
				if (DrillDefinition.IsStructurallyValid())
				{
					return Deck;
				}
			}
		}

		return FHyperTwistTrainingDeck();
	}

	FHyperTwistTrainingCaseSelection MakeSingleCaseSelection(
		const FHyperTwistTrainingDeck& SourceDeck,
		const FHyperTwistTrainingCase& TrainingCase,
		const TArray<FHyperTwistProgressionMemory>& Memories,
		const EHyperTwistTrainingDeliveryMode Mode
	)
	{
		FHyperTwistTrainingDeck SingleCaseDeck = SourceDeck;
		SingleCaseDeck.Cases.Reset();
		SingleCaseDeck.Cases.Add(TrainingCase);
		return UHyperTwistTrainingLibrary::SelectNextTrainingCase(
			SingleCaseDeck,
			Memories,
			Mode,
			FString()
		);
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistCubeDeskBound1DrillTimerCouplingTest,
	"HyperTwist.CleanRoom.CubeDesk.Bound1.DrillTimerCoupling",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistCubeDeskBound1DrillTimerCouplingTest::RunTest(const FString& Parameters)
{
	UGameInstance* GameInstance = NewObject<UGameInstance>();
	TestNotNull(TEXT("Game instance outer must be created for subsystem construction."), GameInstance);
	if (GameInstance == nullptr)
	{
		return false;
	}

	UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
	TestNotNull(TEXT("Training subsystem instance must be created."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	FHyperTwistTrainingDeck Deck = HyperTwistCubeDeskBound1TrainingTimerTestInternal::FindSampleMethodDrillDeck();
	TestTrue(TEXT("A structurally valid sample drill deck must be available."), Deck.IsStructurallyValid());
	if (!Deck.IsStructurallyValid())
	{
		return false;
	}

	Deck.TimingPolicy.bInspectionEnabled = false;
	Deck.TimingPolicy.InspectionDurationMs = 0;

	FHyperTwistTrainingRunState RunState = UHyperTwistTrainingLibrary::StartTrainingRun(
		Deck,
		TEXT("bound1-user"),
		TEXT("bound1-session"),
		EHyperTwistTrainingDeliveryMode::Timer
	);
	TestTrue(TEXT("Sample deck must start a structurally valid timer run."), RunState.IsStructurallyValid());
	if (!RunState.IsStructurallyValid())
	{
		return false;
	}

	const FHyperTwistTrainingMethodDrillDefinition DrillDefinition =
		UHyperTwistTrainingLibrary::BuildMethodDrillDefinitionFromDeck(
			Deck,
			EHyperTwistTrainingDrillModeKind::Recognition,
			EHyperTwistTrainingDrillVisibilityPolicy::PromptOnly,
			true,
			EHyperTwistTrainingDrillEntrySource::Catalog
		);
	TestTrue(TEXT("Sample deck must build a structurally valid drill definition."), DrillDefinition.IsStructurallyValid());
	if (!DrillDefinition.IsStructurallyValid())
	{
		return false;
	}

	FHyperTwistTrainingMethodDrillRunState DrillRunState = UHyperTwistTrainingLibrary::StartMethodDrillRun(
		DrillDefinition,
		RunState.Session.UserId,
		TEXT("bound1-drill-run")
	);
	TestTrue(TEXT("Drill run must initialize from the drill definition."), DrillRunState.IsStructurallyValid());
	DrillRunState = UHyperTwistTrainingLibrary::StartNextMethodDrillCase(DrillRunState);
	TestTrue(TEXT("Drill run must advance into an active current case."), DrillRunState.CurrentCase.IsStructurallyValid());
	if (!DrillRunState.CurrentCase.IsStructurallyValid())
	{
		return false;
	}

	const FHyperTwistTrainingCaseSelection MatchingSelection =
		HyperTwistCubeDeskBound1TrainingTimerTestInternal::MakeSingleCaseSelection(
			Deck,
			DrillRunState.CurrentCase,
			RunState.Memories,
			EHyperTwistTrainingDeliveryMode::Timer
		);
	TestTrue(TEXT("Timer run must be able to select the drill current case."), MatchingSelection.IsStructurallyValid());
	if (!MatchingSelection.IsStructurallyValid())
	{
		return false;
	}

	RunState.CurrentSelection = MatchingSelection;
	RunState.Session.CurrentCaseId = MatchingSelection.TrainingCase.CaseId;

	TestTrue(
		TEXT("Test setup must write the active run state into the subsystem."),
		HyperTwistCubeDeskBound1TrainingTimerTestInternal::SetStructProperty(
			TrainingSubsystem,
			TEXT("ActiveRunState"),
			RunState
		)
	);
	TestTrue(
		TEXT("Test setup must mark the subsystem run state as active."),
		HyperTwistCubeDeskBound1TrainingTimerTestInternal::SetBoolProperty(
			TrainingSubsystem,
			TEXT("bHasActiveRun"),
			true
		)
	);
	TestTrue(
		TEXT("Test setup must write the active drill run state into the subsystem."),
		HyperTwistCubeDeskBound1TrainingTimerTestInternal::SetStructProperty(
			TrainingSubsystem,
			TEXT("ActiveMethodDrillRunState"),
			DrillRunState
		)
	);
	TestTrue(
		TEXT("Test setup must mark the subsystem drill run as active."),
		HyperTwistCubeDeskBound1TrainingTimerTestInternal::SetBoolProperty(
			TrainingSubsystem,
			TEXT("bHasActiveMethodDrillRun"),
			true
		)
	);

	TestTrue(
		TEXT("Drill-aware timer start must succeed when the active drill case matches the timer case."),
		TrainingSubsystem->StartActiveLiveTimerForCurrentDrillCase()
	);

	const FHyperTwistTrainingLiveTimerState StartedTimerState = TrainingSubsystem->GetActiveLiveTimerState();
	TestTrue(TEXT("Timer state must be active after a successful drill-aware start."), StartedTimerState.IsStructurallyValid());
	TestEqual(
		TEXT("Drill-aware timer start must bind the timer to the drill case."),
		StartedTimerState.CaseId,
		DrillRunState.CurrentCase.CaseId
	);
	TestEqual(
		TEXT("Inspection-disabled drill-aware starts must enter the solving phase directly."),
		StartedTimerState.Phase,
		EHyperTwistTrainingLiveTimerPhase::Solving
	);

	TestNotNull(
		TEXT("Subsystem class must expose the drill timer completion delegate surface."),
		TrainingSubsystem->GetClass()->FindPropertyByName(TEXT("OnDrillTimerCompleted"))
	);

	const FHyperTwistTrainingDrillTimerCompletionResult CompletionResult =
		TrainingSubsystem->SubmitAndAdvanceActiveDrillTimerAttempt(EHyperTwistTrainingAttemptResult::Success);

	TestTrue(TEXT("Drill-aware timer completion must submit the active timed attempt."), CompletionResult.bSubmitSucceeded);
	TestTrue(TEXT("Drill-aware timer completion must return a structurally valid submit step."), CompletionResult.SubmitStepResult.IsStructurallyValid());
	TestTrue(TEXT("Drill-aware timer completion must advance the drill after submit."), CompletionResult.bAdvanceSucceeded);
	TestTrue(TEXT("Drill-aware timer completion result must stay structurally valid."), CompletionResult.IsStructurallyValid());
	TestTrue(
		TEXT("Drill advance must retain at least one completed case after submit."),
		CompletionResult.UpdatedDrillRunState.CompletedCaseIds.Num() > 0
	);
	TestEqual(
		TEXT("Advancing the drill must record the completed case id."),
		CompletionResult.UpdatedDrillRunState.CompletedCaseIds[0],
		DrillRunState.CurrentCase.CaseId
	);

	const FHyperTwistTrainingLiveTimerState CompletedTimerState = TrainingSubsystem->GetActiveLiveTimerState();
	TestFalse(TEXT("Successful drill timer completion must clear the active live timer flag."), TrainingSubsystem->HasActiveLiveTimer());
	TestEqual(
		TEXT("Successful drill timer completion must reset the timer phase to idle."),
		CompletedTimerState.Phase,
		EHyperTwistTrainingLiveTimerPhase::Idle
	);

	const FHyperTwistTrainingMethodDrillRunState UpdatedSubsystemDrillState = TrainingSubsystem->GetActiveMethodDrillRunState();
	TestEqual(
		TEXT("Subsystem drill state must advance to the same completed case list as the returned result."),
		UpdatedSubsystemDrillState.CompletedCaseIds.Num(),
		CompletionResult.UpdatedDrillRunState.CompletedCaseIds.Num()
	);

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistCubeDeskBound1DashboardFallbackTest,
	"HyperTwist.CleanRoom.CubeDesk.Bound1.DashboardFallback",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistCubeDeskBound1DashboardFallbackTest::RunTest(const FString& Parameters)
{
	const FString DashboardSourcePath = FPaths::Combine(
		FPaths::ProjectDir(),
		TEXT("Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistCoachDashboardWidget.cpp")
	);
	const FString SubsystemSourcePath = FPaths::Combine(
		FPaths::ProjectDir(),
		TEXT("Source/UnrealHyperTwist/Private/HyperTwistTraining/HyperTwistTrainingSubsystem.cpp")
	);

	FString DashboardSource;
	TestTrue(
		TEXT("Dashboard source must be readable for fallback wiring inspection."),
		FFileHelper::LoadFileToString(DashboardSource, *DashboardSourcePath)
	);
	if (DashboardSource.IsEmpty())
	{
		return false;
	}

	FString SubsystemSource;
	TestTrue(
		TEXT("Subsystem source must be readable for drill completion broadcast inspection."),
		FFileHelper::LoadFileToString(SubsystemSource, *SubsystemSourcePath)
	);
	if (SubsystemSource.IsEmpty())
	{
		return false;
	}

	TestTrue(
		TEXT("Dashboard timer start must prefer the drill-aware timer entry point."),
		DashboardSource.Contains(TEXT("StartActiveLiveTimerForCurrentDrillCase"))
	);
	TestTrue(
		TEXT("Dashboard timer start must retain the generic timer fallback path."),
		DashboardSource.Contains(TEXT("bStarted = TrainingSubsystem->StartActiveLiveTimer();"))
	);
	TestTrue(
		TEXT("Dashboard timed submit must prefer the drill-aware submit-and-advance path."),
		DashboardSource.Contains(TEXT("SubmitAndAdvanceActiveDrillTimerAttempt(Result)"))
	);
	TestTrue(
		TEXT("Dashboard timed submit must retain the generic timed-attempt fallback path."),
		DashboardSource.Contains(TEXT("StepResult = TrainingSubsystem->SubmitActiveLiveTimedAttempt(Result);"))
	);
	TestTrue(
		TEXT("Subsystem drill completion must broadcast the drill timer completion result."),
		SubsystemSource.Contains(TEXT("OnDrillTimerCompleted.Broadcast(CompletionResult);"))
	);

	return true;
}

#endif // WITH_AUTOMATION_TESTS
