// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

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

#if WITH_AUTOMATION_TESTS

namespace HyperTwistWhisperCppPhase6RVTestInternal
{
	FHyperTwistTrainingDeck MakeSpeechDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-v/whispercpp-permission-readiness");
		Deck.Title = TEXT("Phase 6R-V Whisper Permission Readiness");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

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

	void ForceSpeechClientKind(UHyperTwistTrainingSubsystem* TrainingSubsystem, const FString& ClientKind)
	{
		if (TrainingSubsystem == nullptr)
		{
			return;
		}

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

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

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

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

	UHyperTwistHttpSpeechClient* ResolveHttpSpeechClient(UHyperTwistTrainingSubsystem* TrainingSubsystem)
	{
		if (TrainingSubsystem == nullptr)
		{
			return nullptr;
		}

		FString CloseError;
		TrainingSubsystem->CloseActiveCompanionSpeechSession(CloseError);

		if (FObjectPropertyBase* ClientProperty = FindFProperty<FObjectPropertyBase>(
			UHyperTwistTrainingSubsystem::StaticClass(),
			TEXT("ActiveCompanionSpeechClientObject")
		))
		{
			return Cast<UHyperTwistHttpSpeechClient>(
				ClientProperty->GetObjectPropertyValue_InContainer(TrainingSubsystem)
			);
		}

		return nullptr;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RVPermissionReadinessSessionConfigTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.V.PermissionReadinessSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	FString OpenError;
	TestTrue(TEXT("The permission/readiness shell route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
	TestTrue(TEXT("Opening the speech session must not report an error."), OpenError.IsEmpty());

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	const FHyperTwistSpeechMicrophoneShellProfile& Profile =
		SessionState.SessionConfig.MicrophoneShellProfileDefinition;

	TestTrue(TEXT("The permission/readiness route must expose a structurally valid shell profile."), Profile.IsStructurallyValid());
	TestEqual(TEXT("The shell profile must preserve the permission contract id."), Profile.PermissionContractId, TEXT("device-permission-shell-v1"));
	TestEqual(TEXT("The shell profile must preserve the capture-route contract id."), Profile.CaptureRouteContractId, TEXT("capture-route-readiness-shell-v1"));
	TestEqual(TEXT("The shell profile must preserve the permission request action id."), Profile.PermissionRequestActionId, TEXT("request-microphone-permission"));
	TestEqual(TEXT("The shell profile must preserve the capture-route retry action id."), Profile.CaptureRouteRetryActionId, TEXT("retry-capture-route"));
	TestTrue(TEXT("The shell profile must advertise permission-request support."), Profile.bSupportsPermissionRequest);
	TestTrue(TEXT("The shell profile must advertise capture-route retry support."), Profile.bSupportsCaptureRouteRetry);
	TestTrue(
		TEXT("The speech service health must expose device-permission shell capability."),
		SessionState.ServiceHealth.Capabilities.Contains(TEXT("devicePermissionShell"))
	);
	TestTrue(
		TEXT("The speech service health must expose capture-route readiness shell capability."),
		SessionState.ServiceHealth.Capabilities.Contains(TEXT("captureRouteReadinessShell"))
	);
	TestTrue(
		TEXT("The speech service health must expose capture-route retry capability."),
		SessionState.ServiceHealth.Capabilities.Contains(TEXT("captureRouteRetry"))
	);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RVPermissionReadinessOpenStateTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.V.PermissionReadinessOpenState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistWhisperCppPhase6RVPermissionReadinessOpenStateTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistWhisperCppPhase6RVTestInternal::MakeSpeechSubsystem(
			TEXT("phase6r-v-open-session"),
			TEXT("mock")
		);
	TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-V."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	FString OpenError;
	TestTrue(TEXT("The permission/readiness shell route must open a speech session."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
	TestTrue(TEXT("Opening the speech session must not report an error."), OpenError.IsEmpty());

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	const FHyperTwistSpeechMicrophoneShellState& ShellState = SessionState.MicrophoneShellState;

	TestTrue(TEXT("The permission/readiness open state must be structurally valid."), ShellState.IsStructurallyValid());
	TestEqual(TEXT("The open state must preserve granted permission posture."), ShellState.PermissionStateId, TEXT("permission-granted"));
	TestEqual(TEXT("The open state must preserve route-ready posture."), ShellState.CaptureRouteStateId, TEXT("route-ready"));
	TestEqual(TEXT("The open state must expose the ready status line."), ShellState.StatusLine, TEXT("Microphone route ready for coach commands."));
	TestTrue(TEXT("The open state must keep microphone permission granted."), ShellState.bMicrophonePermissionGranted);
	TestTrue(TEXT("The open state must keep the capture route ready."), ShellState.bCaptureRouteReady);
	TestFalse(TEXT("The open state must keep the permission prompt hidden."), ShellState.bPermissionPromptVisible);
	TestFalse(TEXT("The open state must not surface a permission-request action."), ShellState.bPermissionRequestVisible);
	TestFalse(TEXT("The open state must not surface a capture-route retry action."), ShellState.bCaptureRouteRetryVisible);
	TestEqual(TEXT("The open state must not report active shell issues."), ShellState.ActiveIssueCount, 0);
	TestTrue(TEXT("The open state must keep the bounded shell action count unchanged."), ShellState.AvailableActionIds.Num() == 4);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RVCaptureRouteFallbackStateTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.V.CaptureRouteFallbackState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistWhisperCppPhase6RVCaptureRouteFallbackStateTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistWhisperCppPhase6RVTestInternal::MakeSpeechSubsystem(
			TEXT("phase6r-v-route-session"),
			TEXT("http-sidecar")
		);
	TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-V."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	UHyperTwistHttpSpeechClient* HttpSpeechClient =
		HyperTwistWhisperCppPhase6RVTestInternal::ResolveHttpSpeechClient(TrainingSubsystem);
	TestNotNull(TEXT("The provider-backed speech client must be available for the readiness fallback test."), HttpSpeechClient);
	if (HttpSpeechClient == nullptr)
	{
		return false;
	}

	HttpSpeechClient->ServiceBaseUrl.Reset();

	FString OpenError;
	TestFalse(TEXT("The readiness fallback route must fail cleanly when no provider endpoint is configured."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
	TestEqual(TEXT("The fallback route must report the missing service base URL."), OpenError, TEXT("service-base-url-missing"));

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	const FHyperTwistSpeechMicrophoneShellState& ShellState = SessionState.MicrophoneShellState;

	TestTrue(TEXT("The readiness fallback shell state must remain structurally valid."), ShellState.IsStructurallyValid());
	TestEqual(TEXT("The fallback shell must keep permission granted when the failure is route-only."), ShellState.PermissionStateId, TEXT("permission-granted"));
	TestEqual(TEXT("The fallback shell must surface route retry posture."), ShellState.CaptureRouteStateId, TEXT("route-retry-required"));
	TestFalse(TEXT("The fallback shell must report the capture route as not ready."), ShellState.bCaptureRouteReady);
	TestFalse(TEXT("The fallback shell must keep the permission prompt hidden for route failures."), ShellState.bPermissionPromptVisible);
	TestTrue(TEXT("The fallback shell must surface capture-route retry."), ShellState.bCaptureRouteRetryVisible);
	TestEqual(TEXT("The fallback shell must carry the route error."), ShellState.LastRouteError, TEXT("service-base-url-missing"));
	TestEqual(TEXT("The fallback shell must report one active shell issue."), ShellState.ActiveIssueCount, 1);
	TestEqual(TEXT("The fallback shell must classify the issue as capture-route unavailable."), ShellState.ActiveIssue.IssueKind, TEXT("capture-route-unavailable"));
	TestEqual(TEXT("The fallback shell must recommend route retry."), ShellState.ActiveIssue.RecommendedActionId, TEXT("retry-capture-route"));
	TestTrue(
		TEXT("The provider-backed health must still advertise device-permission shell capability."),
		SessionState.ServiceHealth.Capabilities.Contains(TEXT("devicePermissionShell"))
	);
	TestTrue(
		TEXT("The provider-backed health must still advertise capture-route readiness shell capability."),
		SessionState.ServiceHealth.Capabilities.Contains(TEXT("captureRouteReadinessShell"))
	);
	return true;
}

#endif
