// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

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

#if WITH_AUTOMATION_TESTS

namespace HyperTwistWhisperCppPhase6RAGTestInternal
{
	FHyperTwistTrainingDeck MakeSpeechDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-ag/whispercpp-native-capture-route-workflow");
		Deck.Title = TEXT("Phase 6R-AG Whisper Native Capture Route Workflow");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

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

	FHyperTwistTrainingCompanionSpeechSessionState* ResolveActiveSpeechSessionState(
		UHyperTwistTrainingSubsystem* TrainingSubsystem
	)
	{
		if (TrainingSubsystem == nullptr)
		{
			return nullptr;
		}

		if (FStructProperty* SessionStateProperty = FindFProperty<FStructProperty>(
			UHyperTwistTrainingSubsystem::StaticClass(),
			TEXT("ActiveCompanionSpeechSessionState")
		))
		{
			return SessionStateProperty->ContainerPtrToValuePtr<FHyperTwistTrainingCompanionSpeechSessionState>(
				TrainingSubsystem
			);
		}

		return nullptr;
	}

	void ForcePermissionError(UHyperTwistTrainingSubsystem* TrainingSubsystem, const FString& ErrorText)
	{
		if (FHyperTwistTrainingCompanionSpeechSessionState* SessionState =
				ResolveActiveSpeechSessionState(TrainingSubsystem))
		{
			SessionState->LastError = ErrorText;
			SessionState->ServiceHealth.LastError = ErrorText;
		}
	}

	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;
	}

	void ForcePreparationState(
		UHyperTwistTrainingSubsystem* TrainingSubsystem,
		const FString& SessionId
	)
	{
		if (FHyperTwistTrainingCompanionSpeechSessionState* SessionState =
				ResolveActiveSpeechSessionState(TrainingSubsystem))
		{
			SessionState->bSessionOpen = true;
			SessionState->ActiveSessionId = SessionId;
			SessionState->OpenedAtUtc = FDateTime::UtcNow().ToIso8601();
			SessionState->LastUpdatedAtUtc = SessionState->OpenedAtUtc;
			SessionState->SessionConfig = UHyperTwistContractLibrary::MakeSampleSpeechSessionConfig();
			SessionState->SessionConfig.SessionId = SessionId;
			SessionState->LastError = TEXT("service-base-url-missing");
		}
	}

	void ForceRetryState(
		UHyperTwistTrainingSubsystem* TrainingSubsystem,
		const FString& SessionId,
		const FString& ErrorText
	)
	{
		if (FHyperTwistTrainingCompanionSpeechSessionState* SessionState =
				ResolveActiveSpeechSessionState(TrainingSubsystem))
		{
			SessionState->bSessionOpen = true;
			SessionState->ActiveSessionId = SessionId;
			SessionState->OpenedAtUtc = FDateTime::UtcNow().ToIso8601();
			SessionState->LastUpdatedAtUtc = SessionState->OpenedAtUtc;
			SessionState->SessionConfig = UHyperTwistContractLibrary::MakeSampleSpeechSessionConfig();
			SessionState->SessionConfig.SessionId = SessionId;
			SessionState->LastError = ErrorText;
			SessionState->ServiceHealth.LastError = ErrorText;
			SessionState->ServiceHealth.bReady = false;
		}
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RAGNativeCaptureRouteWorkflowSessionConfigTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.AG.NativeCaptureRouteWorkflowSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	FString OpenError;
	TestTrue(TEXT("The native capture-route workflow 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 FHyperTwistSpeechNativeCaptureRouteWorkflowProfile& Profile =
		SessionState.SessionConfig.NativeCaptureRouteWorkflowProfileDefinition;

	TestTrue(TEXT("The native capture-route workflow route must expose a structurally valid profile."), Profile.IsStructurallyValid());
	TestEqual(TEXT("The workflow profile must preserve the first-party profile id."), Profile.NativeCaptureRouteWorkflowProfileId, TEXT("speech-native-capture-route-workflow-v1"));
	TestEqual(TEXT("The workflow profile must preserve the workflow kind."), Profile.WorkflowKind, TEXT("first-party-native-capture-route-workflow"));
	TestEqual(TEXT("The workflow profile must preserve the ownership contract id."), Profile.OwnershipContractId, TEXT("native-capture-route-ownership-v1"));
	TestEqual(TEXT("The workflow profile must preserve the preparation contract id."), Profile.PreparationContractId, TEXT("native-capture-route-preparation-v1"));
	TestEqual(TEXT("The workflow profile must preserve the reopen contract id."), Profile.SessionReopenContractId, TEXT("native-capture-route-session-reopen-v1"));
	TestEqual(TEXT("The workflow profile must preserve the route inspect action id."), Profile.RouteInspectActionId, TEXT("inspect-native-capture-route"));
	TestEqual(TEXT("The workflow profile must preserve the retry action id."), Profile.PreparationRetryActionId, TEXT("retry-capture-route"));
	TestEqual(TEXT("The workflow profile must preserve the reopen action id."), Profile.SessionReopenActionId, TEXT("reopen-speech-session"));
	TestEqual(TEXT("The workflow profile must preserve the permission dependency action id."), Profile.PermissionDependencyActionId, TEXT("resolve-device-permission-workflow"));
	TestTrue(TEXT("The speech service health must expose native capture-route workflow capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("nativeCaptureRouteWorkflow")));
	TestTrue(TEXT("The speech service health must expose capture-route preparation control capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("captureRoutePreparationControl")));
	TestTrue(TEXT("The speech service health must expose capture-route ownership assessment capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("captureRouteOwnershipAssessment")));
	TestTrue(TEXT("The speech service health must expose speech-session reopen capability."), SessionState.ServiceHealth.Capabilities.Contains(TEXT("reopenSpeechSession")));
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RAGNativeCaptureRouteWorkflowClearedStateTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.AG.NativeCaptureRouteWorkflowClearedState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

	FString OpenError;
	TestTrue(TEXT("The native capture-route workflow 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 FHyperTwistSpeechNativeCaptureRouteWorkflowState& WorkflowState =
		SessionState.NativeCaptureRouteWorkflowState;

	TestTrue(TEXT("The cleared native capture-route workflow state must be structurally valid."), WorkflowState.IsStructurallyValid());
	TestEqual(TEXT("The cleared workflow must preserve the cleared workflow state id."), WorkflowState.WorkflowStateId, TEXT("capture-route-workflow-cleared"));
	TestEqual(TEXT("The cleared workflow must preserve route-ready posture."), WorkflowState.CaptureRouteStateId, TEXT("route-ready"));
	TestEqual(TEXT("The cleared workflow must preserve permission-granted posture."), WorkflowState.PermissionStateId, TEXT("permission-granted"));
	TestEqual(TEXT("The cleared workflow must preserve the ready source kind."), WorkflowState.LatestSourceKind, TEXT("capture-route-ready"));
	TestTrue(TEXT("The cleared workflow must confirm route ownership."), WorkflowState.bRouteOwnershipConfirmed);
	TestTrue(TEXT("The cleared workflow must preserve workflow-ready posture."), WorkflowState.bWorkflowReady);
	TestFalse(TEXT("The cleared workflow must not require preparation."), WorkflowState.bPreparationRequired);
	TestFalse(TEXT("The cleared workflow must not require session reopen."), WorkflowState.bSessionOpenRequired);
	TestFalse(TEXT("The cleared workflow must not block on permission workflow."), WorkflowState.bPermissionWorkflowBlocked);
	TestEqual(TEXT("The cleared workflow must not surface active issues."), WorkflowState.ActiveIssueCount, 0);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RAGNativeCaptureRouteWorkflowPermissionDependencyTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.AG.NativeCaptureRouteWorkflowPermissionDependency",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

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

	HyperTwistWhisperCppPhase6RAGTestInternal::ForcePermissionError(
		TrainingSubsystem,
		TEXT("microphone-permission-required")
	);
	TrainingSubsystem->RefreshCompanionSpeechServiceHealthForAutomation();

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	const FHyperTwistSpeechNativeCaptureRouteWorkflowState& WorkflowState =
		SessionState.NativeCaptureRouteWorkflowState;

	TestTrue(TEXT("The permission-dependent native capture-route workflow state must remain structurally valid."), WorkflowState.IsStructurallyValid());
	TestEqual(TEXT("The permission-dependent workflow must preserve the waiting workflow state id."), WorkflowState.WorkflowStateId, TEXT("capture-route-awaiting-permission"));
	TestEqual(TEXT("The permission-dependent workflow must preserve the waiting capture-route posture."), WorkflowState.CaptureRouteStateId, TEXT("route-waiting-for-permission"));
	TestEqual(TEXT("The permission-dependent workflow must preserve permission-required posture."), WorkflowState.PermissionStateId, TEXT("permission-required"));
	TestEqual(TEXT("The permission-dependent workflow must preserve the permission-dependency source kind."), WorkflowState.LatestSourceKind, TEXT("permission-workflow-dependency"));
	TestFalse(TEXT("The permission-dependent workflow must clear ownership confirmation."), WorkflowState.bRouteOwnershipConfirmed);
	TestFalse(TEXT("The permission-dependent workflow must block workflow-ready posture."), WorkflowState.bWorkflowReady);
	TestTrue(TEXT("The permission-dependent workflow must block on the permission workflow."), WorkflowState.bPermissionWorkflowBlocked);
	TestEqual(TEXT("The permission-dependent workflow must retain one workflow entry."), WorkflowState.WorkflowEntryCount, 1);
	TestEqual(TEXT("The permission-dependent workflow must retain one permission-dependency entry."), WorkflowState.PermissionDependencyEntryCount, 1);
	TestEqual(TEXT("The permission-dependent workflow must classify the active issue correctly."), WorkflowState.ActiveIssue.IssueKind, TEXT("permission-workflow-dependency"));
	TestEqual(TEXT("The permission-dependent workflow must recommend the bounded permission workflow action."), WorkflowState.ActiveIssue.RecommendedActionId, TEXT("resolve-device-permission-workflow"));
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RAGNativeCaptureRouteWorkflowPreparationStateTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.AG.NativeCaptureRouteWorkflowPreparationState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

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

	HttpSpeechClient->ServiceBaseUrl.Reset();
	HyperTwistWhisperCppPhase6RAGTestInternal::ForcePreparationState(
		TrainingSubsystem,
		TEXT("phase6r-ag-preparation-session")
	);
	TrainingSubsystem->RefreshCompanionSpeechServiceHealthForAutomation();

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	const FHyperTwistSpeechNativeCaptureRouteWorkflowState& WorkflowState =
		SessionState.NativeCaptureRouteWorkflowState;

	TestTrue(TEXT("The preparation-required native capture-route workflow state must remain structurally valid."), WorkflowState.IsStructurallyValid());
	TestEqual(TEXT("The preparation-required workflow must preserve the preparation workflow state id."), WorkflowState.WorkflowStateId, TEXT("capture-route-preparation-required"));
	TestEqual(TEXT("The preparation-required workflow must preserve the awaiting-endpoint route posture."), WorkflowState.CaptureRouteStateId, TEXT("route-awaiting-endpoint"));
	TestEqual(TEXT("The preparation-required workflow must preserve the preparation source kind."), WorkflowState.LatestSourceKind, TEXT("capture-route-preparation-required"));
	TestFalse(TEXT("The preparation-required workflow must clear ownership confirmation."), WorkflowState.bRouteOwnershipConfirmed);
	TestFalse(TEXT("The preparation-required workflow must block workflow-ready posture."), WorkflowState.bWorkflowReady);
	TestTrue(TEXT("The preparation-required workflow must report preparation required."), WorkflowState.bPreparationRequired);
	TestTrue(TEXT("The preparation-required workflow must surface route inspection."), WorkflowState.bRouteInspectVisible);
	TestEqual(TEXT("The preparation-required workflow must retain one workflow entry."), WorkflowState.WorkflowEntryCount, 1);
	TestEqual(TEXT("The preparation-required workflow must retain one preparation entry."), WorkflowState.PreparationEntryCount, 1);
	TestEqual(TEXT("The preparation-required workflow must classify the active issue correctly."), WorkflowState.ActiveIssue.IssueKind, TEXT("capture-route-preparation-required"));
	TestEqual(TEXT("The preparation-required workflow must carry the missing-endpoint route error."), WorkflowState.LastRouteError, TEXT("service-base-url-missing"));
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RAGNativeCaptureRouteWorkflowRetryStateTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.AG.NativeCaptureRouteWorkflowRetryState",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistWhisperCppPhase6RAGNativeCaptureRouteWorkflowRetryStateTest::RunTest(
	const FString& Parameters
)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistWhisperCppPhase6RAGTestInternal::MakeSpeechSubsystem(
			TEXT("phase6r-ag-retry-session"),
			TEXT("http-sidecar")
		);
	TestNotNull(TEXT("The speech subsystem must be constructed for the retry workflow test."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

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

	HttpSpeechClient->RequestTimeoutSeconds = 0.25f;
	HyperTwistWhisperCppPhase6RAGTestInternal::ForceRetryState(
		TrainingSubsystem,
		TEXT("phase6r-ag-retry-session"),
		TEXT("request-failed")
	);
	TrainingSubsystem->RefreshCompanionSpeechServiceHealthForAutomation();

	const FHyperTwistSpeechNativeCaptureRouteWorkflowState& WorkflowState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState().NativeCaptureRouteWorkflowState;

	TestTrue(TEXT("The retry-required native capture-route workflow state must remain structurally valid."), WorkflowState.IsStructurallyValid());
	TestEqual(TEXT("The retry-required workflow must preserve the retry workflow state id."), WorkflowState.WorkflowStateId, TEXT("capture-route-retry-required"));
	TestEqual(TEXT("The retry-required workflow must preserve the retry capture-route posture."), WorkflowState.CaptureRouteStateId, TEXT("route-retry-required"));
	TestEqual(TEXT("The retry-required workflow must preserve the retry source kind."), WorkflowState.LatestSourceKind, TEXT("capture-route-retry-required"));
	TestFalse(TEXT("The retry-required workflow must clear ownership confirmation."), WorkflowState.bRouteOwnershipConfirmed);
	TestFalse(TEXT("The retry-required workflow must block workflow-ready posture."), WorkflowState.bWorkflowReady);
	TestTrue(TEXT("The retry-required workflow must surface the retry action."), WorkflowState.bPreparationRetryVisible);
	TestEqual(TEXT("The retry-required workflow must retain one workflow entry."), WorkflowState.WorkflowEntryCount, 1);
	TestEqual(TEXT("The retry-required workflow must retain one retry entry."), WorkflowState.RouteRetryEntryCount, 1);
	TestEqual(TEXT("The retry-required workflow must classify the active issue correctly."), WorkflowState.ActiveIssue.IssueKind, TEXT("capture-route-retry-required"));
	TestEqual(TEXT("The retry-required workflow must recommend the bounded retry action."), WorkflowState.ActiveIssue.RecommendedActionId, TEXT("retry-capture-route"));
	TestEqual(TEXT("The retry-required workflow must preserve the provider-backed transport failure."), WorkflowState.LastRouteError, TEXT("request-failed"));
	return true;
}

#endif
