// Copyright HyperTwist, Inc. All Rights Reserved.

#include "Misc/AutomationTest.h"

#include "Components/AudioComponent.h"
#include "Engine/GameInstance.h"
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
#include "HyperTwistRecognition/HyperTwistSpeechLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingSubsystem.h"
#include "UObject/UnrealType.h"

#if WITH_AUTOMATION_TESTS

namespace HyperTwistSpeechLibraryTestInternal
{
	FHyperTwistTrainingDeck MakeSpeechDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("speech-library/coach-deck");
		Deck.Title = TEXT("Speech Library Coach Deck");
		Deck.DeliveryModes = { EHyperTwistTrainingDeliveryMode::CoachReviewed };

		FHyperTwistTrainingCase TrainingCase;
		TrainingCase.CaseId = TEXT("speech-library-case");
		TrainingCase.PuzzleId = TEXT("cube/3x3x3");
		TrainingCase.PromptKind = EHyperTwistTrainingPromptKind::Sequence;
		TrainingCase.PromptLabel = TEXT("Repeat case");
		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"));
		}
	}

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

		if (FStrProperty* RecognitionClientKindProperty = FindFProperty<FStrProperty>(
				UHyperTwistTrainingSubsystem::StaticClass(),
				TEXT("RecognitionClientKind")))
		{
			RecognitionClientKindProperty->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);
		ForceMockRecognitionClient(TrainingSubsystem);
		const FHyperTwistTrainingRunState RunState = TrainingSubsystem->StartTrainingRunFromDeck(
			MakeSpeechDeck(),
			TEXT("speech-library-user"),
			SessionId,
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		);
		return RunState.IsStructurallyValid() ? TrainingSubsystem : nullptr;
	}
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistSpeechLibraryDictationAudioDuckingTest,
	"HyperTwist.Permissive.Speech.Library.DictationAudioDucking",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistSpeechLibraryDictationAudioDuckingTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistSpeechLibraryTestInternal::MakeSpeechSubsystem(TEXT("speech-library-ducking-session"));
	TestNotNull(TEXT("The speech training subsystem must exist."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	UAudioComponent* MusicComponent = NewObject<UAudioComponent>();
	UAudioComponent* SpeechComponent = NewObject<UAudioComponent>();
	TestNotNull(TEXT("The managed music component must exist."), MusicComponent);
	TestNotNull(TEXT("The managed speech component must exist."), SpeechComponent);
	if (MusicComponent == nullptr || SpeechComponent == nullptr)
	{
		return false;
	}

	MusicComponent->SetVolumeMultiplier(0.8f);
	SpeechComponent->SetVolumeMultiplier(1.0f);
	UHyperTwistSpeechLibrary::RegisterCategorizedAudioComponent(
		MusicComponent,
		EHyperTwistManagedAudioCategory::Music);
	UHyperTwistSpeechLibrary::RegisterManagedAudioComponent(SpeechComponent, true);
	UHyperTwistSpeechLibrary::ApplyManagedAudioCategoryVolumes(0.5f, 0.75f, 0.6f);
	TestTrue(
		TEXT("The music category must multiply the authored component level."),
		FMath::IsNearlyEqual(MusicComponent->VolumeMultiplier, 0.4f));
	TestTrue(
		TEXT("The voice category must multiply the authored speech level."),
		FMath::IsNearlyEqual(SpeechComponent->VolumeMultiplier, 0.6f));

	FString OpenError;
	TestTrue(
		TEXT("The speech library must open a dictation session."),
		UHyperTwistSpeechLibrary::StartDictationSession(TrainingSubsystem, OpenError, true, 0.15f)
	);
	TestTrue(TEXT("Opening the dictation session must not report an error."), OpenError.IsEmpty());
	TestTrue(
		TEXT("Managed non-speech audio must be ducked."),
		FMath::IsNearlyEqual(MusicComponent->VolumeMultiplier, 0.06f)
	);
	TestTrue(
		TEXT("Managed speech audio must retain its original volume."),
		FMath::IsNearlyEqual(SpeechComponent->VolumeMultiplier, 0.6f)
	);

	FString CloseError;
	TestTrue(
		TEXT("The speech library must close the dictation session."),
		UHyperTwistSpeechLibrary::EndDictationSession(TrainingSubsystem, CloseError, true)
	);
	TestTrue(TEXT("Closing the dictation session must not report an error."), CloseError.IsEmpty());
	TestTrue(
		TEXT("Managed non-speech audio must restore after dictation ends."),
		FMath::IsNearlyEqual(MusicComponent->VolumeMultiplier, 0.4f)
	);
	TestTrue(
		TEXT("Managed speech audio must remain unchanged after restore."),
		FMath::IsNearlyEqual(SpeechComponent->VolumeMultiplier, 0.6f)
	);

	UHyperTwistSpeechLibrary::UnregisterManagedAudioComponent(MusicComponent);
	UHyperTwistSpeechLibrary::UnregisterManagedAudioComponent(SpeechComponent);
	UHyperTwistSpeechLibrary::ApplyManagedAudioCategoryVolumes(1.0f, 1.0f, 1.0f);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistSpeechLibraryPcmExtractionTest,
	"HyperTwist.Permissive.Speech.Library.PcmExtraction",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistSpeechLibraryPcmExtractionTest::RunTest(const FString& Parameters)
{
	const TArray<uint8> PcmPayload = UHyperTwistSpeechLibrary::Synthesize(
		nullptr,
		TEXT("hello from HyperTwist"),
		TEXT("en_US-lessac-medium"),
		true
	);

	TestTrue(TEXT("The speech library must expose a non-empty PCM payload."), PcmPayload.Num() > 0);
	if (PcmPayload.Num() >= 4)
	{
		const bool bLooksLikeWavHeader =
			PcmPayload[0] == 'R' && PcmPayload[1] == 'I' && PcmPayload[2] == 'F' && PcmPayload[3] == 'F';
		TestFalse(TEXT("The speech library PCM helper must strip the WAV container header."), bLooksLikeWavHeader);
	}

	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistSpeechLibraryNarrationSynthesisTest,
	"HyperTwist.Permissive.Speech.Library.NarrationSynthesis",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistSpeechLibraryNarrationSynthesisTest::RunTest(const FString& Parameters)
{
	FHyperTwistNarrationSynthesisRequest Request =
		UHyperTwistContractLibrary::MakeSampleNarrationSynthesisRequest();
	Request.RequestId = TEXT("speech-library-narration-test");
	Request.ScriptText = TEXT("Solved!");
	Request.SubtitleSeedText = Request.ScriptText;

	const FHyperTwistNarrationSynthesisResult Result =
		UHyperTwistSpeechLibrary::SynthesizeNarration(nullptr, Request, true);
	TestTrue(
		TEXT("The speech library must synthesize a structurally valid narration result."),
		Result.IsStructurallyValid()
	);
	TestTrue(
		TEXT("Narration synthesis must return playable PCM payload bytes."),
		UHyperTwistSpeechLibrary::ExtractPcm16PayloadFromAudioBytes(
			Result.AudioBytes,
			Result.AudioEncoding
		).Num() > 0
	);
	return true;
}

#endif
