// 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 HyperTwistWhisperCppPhase6RETestInternal
{
	FHyperTwistTrainingDeck MakeSpeechDeck()
	{
		FHyperTwistTrainingDeck Deck;
		Deck.DeckId = TEXT("phase6r-e/whispercpp-speech");
		Deck.Title = TEXT("Phase 6R-E Whisper Speech");
		Deck.DeliveryModes = {
			EHyperTwistTrainingDeliveryMode::CoachReviewed
		};

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

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RESessionConfigTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.E.SessionConfig",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

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

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

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	TestEqual(
		TEXT("The speech session must anchor on the existing listening contract."),
		SessionState.SessionConfig.ListeningContractId,
		TEXT("listening-threshold-lifecycle")
	);
	TestEqual(
		TEXT("The speech session must preserve whisper.cpp mono PCM routing."),
		SessionState.SessionConfig.AudioEncoding,
		TEXT("pcm-f32le-16khz-mono")
	);
	TestEqual(
		TEXT("The speech session must keep the bounded coach command grammar profile."),
		SessionState.SessionConfig.GrammarProfileId,
		TEXT("coach-command-grammar-v1")
	);
	TestTrue(TEXT("The speech session must keep VAD enabled."), SessionState.SessionConfig.bEnableVad);
	TestTrue(
		TEXT("The bounded route must preserve repeat-case as a command hint."),
		SessionState.SessionConfig.CommandHints.Contains(TEXT("repeat case"))
	);
	TestTrue(TEXT("The mock speech health must report ready."), SessionState.ServiceHealth.bReady);
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RETranscriptResultTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.E.TranscriptResult",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistWhisperCppPhase6RETranscriptResultTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistWhisperCppPhase6RETestInternal::MakeSpeechSubsystem(TEXT("phase6r-e-transcript-session"));
	TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-E."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	FHyperTwistSpeechUtteranceEnvelope Utterance;
	Utterance.AudioRef = TEXT("mock://coach/repeat-case");
	Utterance.SpeechStartMs = 120;
	Utterance.SpeechEndMs = 860;
	Utterance.SilenceGapMs = 200;
	Utterance.SampleCount = 11840;
	const FHyperTwistSpeechTranscriptResult Result =
		TrainingSubsystem->SubmitActiveCompanionSpeechUtterance(Utterance);

	TestTrue(TEXT("The transcript result must be structurally valid."), Result.IsStructurallyValid());
	TestEqual(TEXT("The bounded mock route must resolve the repeat-case transcript."), Result.TranscriptText, TEXT("repeat case"));
	TestEqual(TEXT("The transcript result must keep one segment."), Result.Segments.Num(), 1);
	if (Result.Segments.Num() > 0)
	{
		TestEqual(TEXT("The transcript segment must preserve the utterance start time."), Result.Segments[0].StartMs, 120);
		TestEqual(TEXT("The transcript segment must preserve the utterance end time."), Result.Segments[0].EndMs, 860);
		TestTrue(TEXT("The transcript segment must stay grammar-constrained."), Result.Segments[0].bGrammarConstrained);
	}

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	TestEqual(TEXT("The active speech state must count one submitted utterance."), SessionState.SubmittedUtteranceCount, 1);
	TestEqual(TEXT("The active speech state must count one final transcript."), SessionState.FinalTranscriptCount, 1);
	TestEqual(TEXT("The active speech state must retain the last transcript text."), SessionState.LastTranscriptResult.TranscriptText, TEXT("repeat case"));
	return true;
}

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
	FHyperTwistWhisperCppPhase6RECloseSessionTest,
	"HyperTwist.Permissive.WhisperCpp.Phase6R.E.CloseSession",
	EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)

bool FHyperTwistWhisperCppPhase6RECloseSessionTest::RunTest(const FString& Parameters)
{
	UHyperTwistTrainingSubsystem* TrainingSubsystem =
		HyperTwistWhisperCppPhase6RETestInternal::MakeSpeechSubsystem(TEXT("phase6r-e-close-session"));
	TestNotNull(TEXT("The speech subsystem must be constructed for Phase 6R-E."), TrainingSubsystem);
	if (TrainingSubsystem == nullptr)
	{
		return false;
	}

	FString OpenError;
	TestTrue(TEXT("The speech session must open cleanly before close."), TrainingSubsystem->OpenActiveCompanionSpeechSession(OpenError));
	TestTrue(TEXT("Opening the speech session must not report an error."), OpenError.IsEmpty());

	FString CloseError;
	TestTrue(TEXT("The speech session must close cleanly."), TrainingSubsystem->CloseActiveCompanionSpeechSession(CloseError));
	TestTrue(TEXT("Closing the speech session must not report an error."), CloseError.IsEmpty());

	const FHyperTwistTrainingCompanionSpeechSessionState SessionState =
		TrainingSubsystem->GetActiveCompanionSpeechSessionState();
	TestFalse(TEXT("The speech session must no longer report as open."), SessionState.bSessionOpen);
	TestTrue(TEXT("The mock speech health should remain available after close."), SessionState.ServiceHealth.bReady);
	TestEqual(TEXT("The mock speech provider label must stay stable."), SessionState.ServiceHealth.ProviderLabel, TEXT("mock"));
	return true;
}

#endif
