// 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 HyperTwistHandyPhase6RAOTestInternal
{
	FHyperTwistTrainingDeck MakeSpeechDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-ao/handy-hotkey-mute-device-shell");
		Deck.Title = TEXT("Phase 6R-AO Handy Hotkey Mute Device Shell");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("phase6r-ao-case");
		TrainingCase.PuzzleId = TEXT("cube/3x3x3");
		TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence;
		TrainingCase.PromptLabel = TEXT("Phase 6R-AO 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-ao-user"),
			SessionId,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		);
		return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr;
	}

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

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

	bool HasProfileActionBinding(
		const TArray<FHyperTwistSpeechShellActionBinding>& ActionBindings,
		const FString& ExpectedActionId
	)
	{
		return ActionBindings.ContainsByPredicate(
			[&ExpectedActionId](const FHyperTwistSpeechShellActionBinding& Binding)
			{
				return Binding.ActionId == ExpectedActionId;
			}
		);
	}
}

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

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

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

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	const FHyperTwistSpeechExternalDictationShellProfile& Profile =
		SessionState.SessionConfig.ExternalDictationShellProfileDefinition;

	TestTrue(TEXT("The external dictation shell profile must remain structurally valid."), Profile.IsStructurallyValid());
	TestEqual(
		TEXT("The global hotkey surface must stay bounded to platform-scoped shortcut lifecycle."),
		Profile.GlobalHotkeySurfaceMode,
		TEXT("platform-scoped-dictation-shortcut-lifecycle")
	);
	TestEqual(
		TEXT("The input-device surface must stay bounded to selected device cycling."),
		Profile.InputDeviceSurfaceMode,
		TEXT("selected-input-device-cycle")
	);
	TestEqual(
		TEXT("The output-device surface must stay bounded to selected device cycling."),
		Profile.OutputDeviceSurfaceMode,
		TEXT("selected-output-device-cycle")
	);
	TestEqual(
		TEXT("The mute surface must stay bounded to mute-while-recording posture."),
		Profile.MuteSurfaceMode,
		TEXT("mute-while-recording-toggle")
	);
	TestEqual(
		TEXT("The microphone mode surface must stay bounded to lazy-close and always-on posture."),
		Profile.MicrophoneModeSurfaceMode,
		TEXT("lazy-close-and-always-on-mode")
	);
	TestEqual(
		TEXT("The retained primary hotkey label must stay aligned to the donor shortcut posture."),
		Profile.PrimaryHotkeyBindingLabel,
		TEXT("ctrl+space")
	);
	TestEqual(
		TEXT("The retained post-process hotkey label must stay aligned to the donor shortcut posture."),
		Profile.PostProcessHotkeyBindingLabel,
		TEXT("ctrl+shift+space")
	);
	TestTrue(
		TEXT("The shell profile must expose the global-hotkey inspection action binding."),
		HyperTwistHandyPhase6RAOTestInternal::HasProfileActionBinding(
			Profile.ActionBindings,
			TEXT("inspect-dictation-global-hotkey")
		)
	);
	TestTrue(
		TEXT("The shell profile must expose the input-device cycle action binding."),
		HyperTwistHandyPhase6RAOTestInternal::HasProfileActionBinding(
			Profile.ActionBindings,
			TEXT("cycle-dictation-input-device")
		)
	);
	TestTrue(
		TEXT("The shell profile must expose the mute toggle action binding."),
		HyperTwistHandyPhase6RAOTestInternal::HasProfileActionBinding(
			Profile.ActionBindings,
			TEXT("toggle-dictation-mute-while-recording")
		)
	);
	TestTrue(
		TEXT("Speech health must expose the global-hotkey shell capability."),
		HyperTwistHandyPhase6RAOTestInternal::HasCapability(
			SessionState.ServiceHealth.Capabilities,
			TEXT("speechGlobalHotkeyShell")
		)
	);
	TestTrue(
		TEXT("Speech health must expose the device-selection shell capability."),
		HyperTwistHandyPhase6RAOTestInternal::HasCapability(
			SessionState.ServiceHealth.Capabilities,
			TEXT("speechDeviceSelectionShell")
		)
	);
	TestTrue(
		TEXT("Speech health must expose the mute-while-recording shell capability."),
		HyperTwistHandyPhase6RAOTestInternal::HasCapability(
			SessionState.ServiceHealth.Capabilities,
			TEXT("speechMuteWhileRecordingShell")
		)
	);
	TestTrue(
		TEXT("Speech health must expose the microphone-mode shell capability."),
		HyperTwistHandyPhase6RAOTestInternal::HasCapability(
			SessionState.ServiceHealth.Capabilities,
			TEXT("speechMicrophoneModeShell")
		)
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistHandyPhase6RAOShellStateTest,
	"HyperTwist.Permissive.Handy.Phase6R.AO.HotkeyMuteDeviceShellState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistHandyPhase6RAOShellStateTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistHandyPhase6RAOTestInternal::MakeSpeechSubsystem(TEXT("phase6r-ao-shell-session"));
	TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-AO."), 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 primary global hotkey label must remain aligned to the retained shortcut posture."),
		ShellState.PrimaryHotkeyBindingLabel,
		TEXT("ctrl+space")
	);
	TestEqual(
		TEXT("The post-process hotkey label must remain aligned to the retained shortcut posture."),
		ShellState.PostProcessHotkeyBindingLabel,
		TEXT("ctrl+shift+space")
	);
	TestEqual(
		TEXT("The selected input device id must default to the system route."),
		ShellState.SelectedInputDeviceId,
		TEXT("system-default-input")
	);
	TestEqual(
		TEXT("The selected output device id must default to the system route."),
		ShellState.SelectedOutputDeviceId,
		TEXT("system-default-output")
	);
	TestEqual(
		TEXT("The microphone mode must default to on-demand."),
		ShellState.MicrophoneModeId,
		TEXT("on-demand")
	);
	TestTrue(TEXT("The shell must surface the global-hotkey card."), ShellState.bGlobalHotkeyVisible);
	TestTrue(TEXT("The shell must surface the input-device selection card."), ShellState.bInputDeviceSelectionVisible);
	TestTrue(TEXT("The shell must surface the output-device selection card."), ShellState.bOutputDeviceSelectionVisible);
	TestTrue(TEXT("The shell must surface the mute-while-recording card."), ShellState.bMuteWhileRecordingVisible);
	TestTrue(TEXT("The shell must surface the microphone-mode card."), ShellState.bMicrophoneModeVisible);
	TestTrue(TEXT("The shell must keep push-to-talk mode enabled by default."), ShellState.bPushToTalkMode);
	TestFalse(TEXT("Mute-while-recording must stay disabled by default."), ShellState.bMuteWhileRecordingEnabled);
	TestEqual(TEXT("The shell must retain three bounded input-device options."), ShellState.InputDeviceOptions.Num(), 3);
	TestEqual(TEXT("The shell must retain two bounded output-device options."), ShellState.OutputDeviceOptions.Num(), 2);
	TestTrue(
		TEXT("The shell must expose the global-hotkey inspection action."),
		HyperTwistHandyPhase6RAOTestInternal::HasActionId(
			ShellState.AvailableActionIds,
			TEXT("inspect-dictation-global-hotkey")
		)
	);
	TestTrue(
		TEXT("The shell must expose the input-device cycle action."),
		HyperTwistHandyPhase6RAOTestInternal::HasActionId(
			ShellState.AvailableActionIds,
			TEXT("cycle-dictation-input-device")
		)
	);
	TestTrue(
		TEXT("The shell must expose the output-device cycle action."),
		HyperTwistHandyPhase6RAOTestInternal::HasActionId(
			ShellState.AvailableActionIds,
			TEXT("cycle-dictation-output-device")
		)
	);
	TestTrue(
		TEXT("The shell must expose the mute-while-recording toggle action."),
		HyperTwistHandyPhase6RAOTestInternal::HasActionId(
			ShellState.AvailableActionIds,
			TEXT("toggle-dictation-mute-while-recording")
		)
	);
	TestTrue(
		TEXT("The shell must expose the microphone-mode toggle action."),
		HyperTwistHandyPhase6RAOTestInternal::HasActionId(
			ShellState.AvailableActionIds,
			TEXT("toggle-dictation-microphone-mode")
		)
	);
	return true;
}

#endif
