// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "Engine/GameInstance.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
#include "UObject/UnrealType.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistHandyPhase6RAJTestInternal
{
	FHyperTwistTrainingDeck MakeSpeechDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-aj/handy-dictation-adjunct");
		Deck.Title = TEXT("Phase 6R-AJ Handy Dictation Adjunct");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("phase6r-aj-case");
		TrainingCase.PuzzleId = TEXT("cube/3x3x3");
		TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence;
		TrainingCase.PromptLabel = TEXT("Phase 6R-AJ Coach Speech");
		TrainingCase.AllowedDeliveryModes = {
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};
		Deck.Cases = {TrainingCase};
		return Deck;
	}

	void ForceMockSpeechClient(UHyperTwistTrainingSubsystem* TrainingSubsystem)
	{
		if (TrainingSubsystem == nullptr)
		{
			return;
		}

		if (FStrProperty* SpeechClientKindProperty = FindFProperty<FStrProperty>(
			UHyperTwistTrainingSubsystem::StaticClass(),
			TEXT("CompanionSpeechClientKind")
		))
		{
			SpeechClientKindProperty->SetPropertyValue_InContainer(TrainingSubsystem, TEXT("mock"));
		}
	}

	UHyperTwistTrainingSubsystem* MakeSpeechSubsystem(const FString& SessionId)
	{
		UGameInstance* GameInstance = NewObject<UGameInstance>();
		if (GameInstance == nullptr)
		{
			return nullptr;
		}

		UHyperTwistTrainingSubsystem* TrainingSubsystem = NewObject<UHyperTwistTrainingSubsystem>(GameInstance);
		if (TrainingSubsystem == nullptr)
		{
			return nullptr;
		}

		ForceMockSpeechClient(TrainingSubsystem);
		const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
			MakeSpeechDeck(),
			TEXT("phase6r-aj-user"),
			SessionId,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		);
		return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr;
	}

	bool HasActionId(const TArray<FString>& ActionIds, const FString& ExpectedActionId)
	{
		return ActionIds.Contains(ExpectedActionId);
	}

	bool HasCapability(const TArray<FString>& Capabilities, const FString& ExpectedCapability)
	{
		return Capabilities.Contains(ExpectedCapability);
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistHandyPhase6RAJSessionConfigTest,
	"HyperTwist.Permissive.Handy.Phase6R.AJ.SessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistHandyPhase6RAJSessionConfigTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistHandyPhase6RAJTestInternal::MakeSpeechSubsystem(TEXT("phase6r-aj-config-session"));
	TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AJ."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	FString OpenError;
	TestTrue(
		TEXT("The companion speech session must open for the Handy adjunct."),
		TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError)
	);
	TestTrue(TEXT("Opening the speech session must not report an error."), OpenError.IsEmpty());

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	TestTrue(TEXT("The speech session config must remain structurally valid."), SessionState.SessionConfig.IsStructurallyValid());
	TestEqual(
		TEXT("The external dictation shell profile id must be present."),
		SessionState.SessionConfig.ExternalDictationShellProfileId,
		TEXT("speech-external-dictation-shell-v1")
	);
	TestTrue(
		TEXT("The external dictation shell profile must remain structurally valid."),
		SessionState.SessionConfig.ExternalDictationShellProfileDefinition.IsStructurallyValid()
	);
	TestEqual(
		TEXT("The shell kind must stay bounded to the external dictation shell."),
		SessionState.SessionConfig.ExternalDictationShellProfileDefinition.ShellKind,
		TEXT("operator-external-dictation-shell")
	);
	TestEqual(
		TEXT("The retained shell must preserve the cycle-route action id."),
		SessionState.SessionConfig.ExternalDictationShellProfileDefinition.CycleOutputRouteActionId,
		TEXT("cycle-external-dictation-output-route")
	);
	TestTrue(
		TEXT("Speech health must expose the external dictation shell capability."),
		HyperTwistHandyPhase6RAJTestInternal::HasCapability(
			SessionState.ServiceHealth.Capabilities,
			TEXT("externalDictationShell")
		)
	);
	TestTrue(
		TEXT("Speech health must expose the transcript-history shell capability."),
		HyperTwistHandyPhase6RAJTestInternal::HasCapability(
			SessionState.ServiceHealth.Capabilities,
			TEXT("speechTranscriptHistoryShell")
		)
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistHandyPhase6RAJShellStateTest,
	"HyperTwist.Permissive.Handy.Phase6R.AJ.ExternalDictationShellState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistHandyPhase6RAJShellStateTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistHandyPhase6RAJTestInternal::MakeSpeechSubsystem(TEXT("phase6r-aj-shell-session"));
	TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AJ."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	FString OpenError;
	TestTrue(TEXT("The companion speech session must open for shell inspection."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
	TestTrue(TEXT("Opening the speech session must not report an error."), OpenError.IsEmpty());

	const FHyperTwistSpeechExternalDictationShellState ShellState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState().ExternalDictationShellState;
	TestTrue(TEXT("The external dictation shell state must remain structurally valid."), ShellState.IsStructurallyValid());
	TestEqual(
		TEXT("The selected route must default to clipboard paste."),
		ShellState.SelectedOutputRouteId,
		TEXT("clipboard-paste")
	);
	TestEqual(TEXT("The bounded shell must expose three retained output routes."), ShellState.OutputRouteOptions.Num(), 3);
	TestTrue(TEXT("The transcript-history surface must stay visible."), ShellState.bTranscriptHistoryVisible);
	TestTrue(TEXT("The output-routing surface must stay visible."), ShellState.bOutputRoutingVisible);
	TestFalse(TEXT("No retained transcript should exist before the first utterance."), ShellState.bLatestEntryReadyForDispatch);
	TestTrue(
		TEXT("The shell must expose the copy-transcript action."),
		HyperTwistHandyPhase6RAJTestInternal::HasActionId(
			ShellState.AvailableActionIds,
			TEXT("copy-latest-dictation-transcript")
		)
	);
	TestTrue(
		TEXT("The shell must expose the script-dispatch action."),
		HyperTwistHandyPhase6RAJTestInternal::HasActionId(
			ShellState.AvailableActionIds,
			TEXT("dispatch-dictation-script-output")
		)
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistHandyPhase6RAJTranscriptHistoryTest,
	"HyperTwist.Permissive.Handy.Phase6R.AJ.TranscriptHistoryOutputRouting",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistHandyPhase6RAJTranscriptHistoryTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistHandyPhase6RAJTestInternal::MakeSpeechSubsystem(
			TEXT("phase6r-aj-transcript-session")
		);
	TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AJ."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	FHyperTwistSpeechUtteranceEnvelope Utterance;
	Utterance.AudioRef = TEXT("mock://coach/repeat-case");
	Utterance.SpeechStartMs = 140;
	Utterance.SpeechEndMs = 920;
	Utterance.SilenceGapMs = 180;
	Utterance.SampleCount = 12480;
	const FHyperTwistSpeechTranscriptResult Result =
		TrainingSubsystem->SubmitActiveCompanionSpeechUtterance(Utterance);

	TestTrue(TEXT("The transcript result must stay structurally valid."), Result.IsStructurallyValid());
	TestEqual(
		TEXT("The bounded mock route must still resolve the repeat-case transcript."),
		Result.TranscriptText,
		TEXT("repeat case")
	);

	const FHyperTwistSpeechExternalDictationShellState ShellState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState().ExternalDictationShellState;
	TestTrue(TEXT("The external dictation shell state must remain structurally valid after transcript capture."), ShellState.IsStructurallyValid());
	TestEqual(TEXT("The shell must retain one transcript-history entry."), ShellState.HistoryEntryCount, 1);
	TestEqual(TEXT("The latest transcript text must stay available for output routing."), ShellState.LatestTranscriptText, TEXT("repeat case"));
	TestEqual(TEXT("The post-processed text must normalize to the same bounded command text."), ShellState.LatestPostProcessedText, TEXT("repeat case"));
	TestTrue(TEXT("The latest retained transcript must be ready for output routing."), ShellState.bLatestEntryReadyForDispatch);
	TestEqual(
		TEXT("The retained transcript must stay aligned to the selected route."),
		ShellState.HistoryEntries.Num() > 0 ? ShellState.HistoryEntries[0].OutputRouteId : FString(),
		TEXT("clipboard-paste")
	);
	return true;
}

#endif
