// 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 HyperTwistWhisperCppPhase6RXTestInternal
{
	FHyperTwistTrainingDeck MakeSpeechDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-x/provider-profile-byok");
		Deck.Title = TEXT("Phase 6R-X Provider Profile And BYOK");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("phase6r-x-case");
		TrainingCase.PuzzleId = TEXT("cube/3x3x3");
		TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence;
		TrainingCase.PromptLabel = TEXT("Phase 6R-X 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-x-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(
	FHyperTwistWhisperCppPhase6RXProviderProfileSessionConfigTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.X.ProviderProfileSessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

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

	const FHyperTwistSpeechSessionConfig& Config =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState().SessionConfig;

	TestTrue(TEXT("The provider-profile session config must remain structurally valid."), Config.IsStructurallyValid());
	TestEqual(TEXT("The mock route must preserve the mock provider profile id."), Config.ProviderProfileId, TEXT("speech-provider/mock-offline-profile-v1"));
	TestTrue(TEXT("The provider-profile session config must expose a structurally valid provider profile."), Config.ProviderProfileDefinition.IsStructurallyValid());
	TestEqual(TEXT("The mock route must preserve the mock provider class."), Config.ProviderProfileDefinition.ProviderClass, TEXT("first-party-mock-provider"));
	TestEqual(TEXT("The mock route must preserve the in-process endpoint class."), Config.ProviderProfileDefinition.EndpointClass, TEXT("in-process-mock"));
	TestEqual(TEXT("The mock route must preserve the no-auth provider posture."), Config.ProviderProfileDefinition.AuthMaterialSource, TEXT("not-required"));
	TestEqual(TEXT("The provider profile must expose two supported model ids."), Config.ProviderProfileDefinition.SupportedModelIds.Num(), 2);
	TestTrue(TEXT("The provider profile must expose the manual transcript capability in its capability map."), Config.ProviderProfileDefinition.CapabilityFlags.Contains(TEXT("manualTranscriptCommit")));

	TestEqual(TEXT("The session config must preserve the BYOK custody profile id."), Config.ByokCustodyProfileId, TEXT("speech-byok-custody-profile-v1"));
	TestTrue(TEXT("The session config must expose a structurally valid BYOK custody profile."), Config.ByokCustodyProfileDefinition.IsStructurallyValid());
	TestTrue(TEXT("The BYOK custody profile must preserve OpenAI-compatible custom endpoint support."), Config.ByokCustodyProfileDefinition.bSupportsOpenAiCompatibleCustomEndpoints);
	TestTrue(TEXT("The BYOK custody profile must preserve user-labeled provider profile support."), Config.ByokCustodyProfileDefinition.bSupportsUserLabeledProfiles);
	TestTrue(TEXT("The BYOK custody profile must preserve the bounded sidecar provider class."), Config.ByokCustodyProfileDefinition.SupportedProviderClasses.Contains(TEXT("bounded-speech-sidecar-service")));
	TestFalse(TEXT("The BYOK custody profile must not allow donor-owned key custody."), Config.ByokCustodyProfileDefinition.bAllowsDonorOwnedKeyCustody);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RXProviderProfileServiceHealthTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.X.ProviderProfileServiceHealth",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

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

	const FHyperTwistSpeechServiceHealth& Health =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState().ServiceHealth;

	TestTrue(TEXT("The provider-profile service health must remain structurally valid."), Health.IsStructurallyValid());
	TestEqual(TEXT("The service health must preserve the active provider profile id."), Health.ProviderProfileId, TEXT("speech-provider/mock-offline-profile-v1"));
	TestTrue(TEXT("The service health must preserve the provider-profile custody capability."), Health.Capabilities.Contains(TEXT("providerProfileCustody")));
	TestTrue(TEXT("The service health must preserve the BYOK custody capability."), Health.Capabilities.Contains(TEXT("byokCustody")));
	TestTrue(TEXT("The service health must preserve the OpenAI-compatible endpoint profile capability."), Health.Capabilities.Contains(TEXT("openAiCompatibleCustomEndpointProfile")));
	TestTrue(TEXT("The service health must preserve the user-labeled BYOK profile capability."), Health.Capabilities.Contains(TEXT("userLabeledByokProfiles")));
	TestTrue(TEXT("The service health must expose a structurally valid BYOK custody profile."), Health.ByokCustodyProfileDefinition.IsStructurallyValid());
	TestTrue(TEXT("The BYOK custody profile must preserve per-profile model maps."), Health.ByokCustodyProfileDefinition.bSupportsPerProfileModelMaps);
	TestTrue(TEXT("The BYOK custody profile must preserve per-profile auth material."), Health.ByokCustodyProfileDefinition.bSupportsPerProfileAuthMaterial);
	TestTrue(TEXT("The BYOK custody profile must preserve profile enable-disable controls."), Health.ByokCustodyProfileDefinition.bSupportsEnableDisableControls);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RXProviderBackedByokFallbackTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.X.ProviderBackedByokFallback",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

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

	HttpSpeechClient->ServiceBaseUrl.Reset();

	FString OpenError;
	TestFalse(TEXT("The provider-backed provider-profile route must fail cleanly when the configured provider endpoint is unavailable."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
	TestEqual(TEXT("The provider-backed provider-profile route must report a provider-backed transport failure once the provider profile endpoint is selected."), OpenError, TEXT("request-failed"));

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	const FHyperTwistSpeechServiceHealth& Health = SessionState.ServiceHealth;

	TestTrue(TEXT("The provider-backed fallback health must remain structurally valid."), Health.IsStructurallyValid());
	TestEqual(TEXT("The provider-backed fallback must keep the local HTTP provider profile id."), Health.ProviderProfileId, TEXT("speech-provider/local-http-sidecar-profile-v1"));
	TestEqual(TEXT("The provider-backed fallback must keep the local self-hosted endpoint class."), Health.ProviderProfileDefinition.EndpointClass, TEXT("local-self-hosted-http"));
	TestEqual(TEXT("The provider-backed fallback must keep the first-party auth custody posture."), Health.ProviderProfileDefinition.AuthMaterialSource, TEXT("first-party-out-of-band-secret-reference"));
	TestTrue(TEXT("The provider-backed fallback must preserve custom base URL support."), Health.ProviderProfileDefinition.bSupportsCustomBaseUrl);
	TestEqual(TEXT("The provider-backed fallback must preserve the provider-profile endpoint base URL when the runtime selects that route."), Health.ProviderProfileDefinition.EndpointBaseUrl, TEXT("http://127.0.0.1:8766"));
	TestFalse(TEXT("The provider-backed fallback must not drop the provider endpoint reference id."), Health.ProviderProfileDefinition.EndpointReferenceId.IsEmpty());
	TestEqual(TEXT("The provider-backed fallback must keep the BYOK custody profile id."), Health.ByokCustodyProfileId, TEXT("speech-byok-custody-profile-v1"));
	TestTrue(TEXT("The provider-backed fallback must preserve provider-profile custody capability."), Health.Capabilities.Contains(TEXT("providerProfileCustody")));
	TestTrue(TEXT("The provider-backed fallback must preserve BYOK custody capability."), Health.Capabilities.Contains(TEXT("byokCustody")));
	TestTrue(TEXT("The provider-backed fallback must preserve OpenAI-compatible custom endpoint support."), Health.ByokCustodyProfileDefinition.bSupportsOpenAiCompatibleCustomEndpoints);
	TestEqual(TEXT("The provider-backed fallback must keep the provider-backed transport failure in the shell state."), SessionState.MicrophoneShellState.LastRouteError, TEXT("request-failed"));
	return true;
}

#endif
