/**
 * Spark AI Voice Chat - Unreal Engine SDK Implementation
 */

#include "SparkVoiceChat.h"
#include "HttpModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "Components/AudioComponent.h"
#include "Kismet/GameplayStatics.h"

USparkVoiceChat::USparkVoiceChat()
{
	PrimaryComponentTick.bCanEverTick = false;
}

void USparkVoiceChat::BeginPlay()
{
	Super::BeginPlay();

	if (SessionId.IsEmpty())
	{
		SessionId = GenerateSessionId();
	}

	// Create audio capture component on the owning actor
	AudioCapture = NewObject<UAudioCaptureComponent>(GetOwner());
	if (AudioCapture)
	{
		AudioCapture->RegisterComponent();
	}
}

void USparkVoiceChat::StartRecording()
{
	if (bIsRecording || !AudioCapture) return;

	RecordedSamples.Empty();
	bIsRecording = true;

	// Bind to audio generation callback
	AudioCapture->OnAudioEnvelopeValueNative.AddLambda(
		[this](const UAudioComponent*, const float Value)
		{
			// Envelope callback - not used for raw samples
		});

	AudioCapture->Start();
}

void USparkVoiceChat::StopRecordingAndSend()
{
	if (!bIsRecording || !AudioCapture) return;

	AudioCapture->Stop();
	bIsRecording = false;

	if (RecordedSamples.Num() == 0)
	{
		OnError.Broadcast(TEXT("No audio recorded"));
		return;
	}

	TArray<uint8> WavData = EncodeToWav(RecordedSamples, 1, SampleRate);
	SendVoiceChat(WavData);
}

TArray<uint8> USparkVoiceChat::EncodeToWav(const TArray<float>& Samples, int32 Channels, int32 Rate)
{
	int32 SampleCount = Samples.Num();
	int32 DataSize = SampleCount * 2; // 16-bit
	int32 FileSize = 44 + DataSize;

	TArray<uint8> Wav;
	Wav.SetNumZeroed(FileSize);

	uint8* P = Wav.GetData();

	// RIFF header
	FMemory::Memcpy(P, "RIFF", 4); P += 4;
	int32 ChunkSize = FileSize - 8;
	FMemory::Memcpy(P, &ChunkSize, 4); P += 4;
	FMemory::Memcpy(P, "WAVE", 4); P += 4;

	// fmt chunk
	FMemory::Memcpy(P, "fmt ", 4); P += 4;
	int32 FmtSize = 16;
	FMemory::Memcpy(P, &FmtSize, 4); P += 4;
	int16 AudioFormat = 1; // PCM
	FMemory::Memcpy(P, &AudioFormat, 2); P += 2;
	int16 NumChannels = (int16)Channels;
	FMemory::Memcpy(P, &NumChannels, 2); P += 2;
	FMemory::Memcpy(P, &Rate, 4); P += 4;
	int32 ByteRate = Rate * Channels * 2;
	FMemory::Memcpy(P, &ByteRate, 4); P += 4;
	int16 BlockAlign = (int16)(Channels * 2);
	FMemory::Memcpy(P, &BlockAlign, 2); P += 2;
	int16 BitsPerSample = 16;
	FMemory::Memcpy(P, &BitsPerSample, 2); P += 2;

	// data chunk
	FMemory::Memcpy(P, "data", 4); P += 4;
	FMemory::Memcpy(P, &DataSize, 4); P += 4;

	// PCM samples
	for (int32 i = 0; i < SampleCount; i++)
	{
		int16 Val = (int16)(FMath::Clamp(Samples[i], -1.0f, 1.0f) * 32767.0f);
		FMemory::Memcpy(P, &Val, 2);
		P += 2;
	}

	return Wav;
}

void USparkVoiceChat::SendVoiceChat(const TArray<uint8>& WavData)
{
	FString Boundary = FString::Printf(TEXT("----SparkBoundary%d"), FMath::RandRange(100000, 999999));

	// Build multipart body
	TArray<uint8> Body;

	auto AppendString = [&Body](const FString& Str)
	{
		FTCHARToUTF8 Converter(*Str);
		Body.Append((const uint8*)Converter.Get(), Converter.Length());
	};

	// Audio file part
	AppendString(FString::Printf(TEXT("--%s\r\n"), *Boundary));
	AppendString(TEXT("Content-Disposition: form-data; name=\"audio\"; filename=\"audio.wav\"\r\n"));
	AppendString(TEXT("Content-Type: audio/wav\r\n\r\n"));
	Body.Append(WavData);
	AppendString(TEXT("\r\n"));

	// Session ID part
	AppendString(FString::Printf(TEXT("--%s\r\n"), *Boundary));
	AppendString(TEXT("Content-Disposition: form-data; name=\"sessionId\"\r\n\r\n"));
	AppendString(SessionId);
	AppendString(TEXT("\r\n"));

	// Closing boundary
	AppendString(FString::Printf(TEXT("--%s--\r\n"), *Boundary));

	// Send HTTP request
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(Endpoint);
	Request->SetVerb(TEXT("POST"));
	Request->SetHeader(TEXT("Content-Type"), FString::Printf(TEXT("multipart/form-data; boundary=%s"), *Boundary));
	Request->SetHeader(TEXT("X-Api-Key"), ApiKey);
	Request->SetContent(Body);
	Request->OnProcessRequestComplete().BindUObject(this, &USparkVoiceChat::OnHttpResponse);
	Request->ProcessRequest();
}

void USparkVoiceChat::OnHttpResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSuccess)
{
	if (!bSuccess || !Response.IsValid() || Response->GetResponseCode() != 200)
	{
		FString ErrorMsg = Response.IsValid() ? Response->GetContentAsString() : TEXT("Request failed");
		OnError.Broadcast(ErrorMsg);
		return;
	}

	// Parse transcript and AI response from headers
	FString Transcript = Response->GetHeader(TEXT("X-Transcript"));
	FString AIResponse = Response->GetHeader(TEXT("X-AI-Response"));

	if (!Transcript.IsEmpty())
	{
		OnTranscriptReceived.Broadcast(FGenericPlatformHttp::UrlDecode(Transcript));
	}
	if (!AIResponse.IsEmpty())
	{
		OnResponseReceived.Broadcast(FGenericPlatformHttp::UrlDecode(AIResponse));
	}

	// Play audio response
	PlayWavResponse(Response->GetContent());
}

void USparkVoiceChat::PlayWavResponse(const TArray<uint8>& WavData)
{
	if (WavData.Num() < 44) return;

	// Parse WAV header
	int16 Channels = *(int16*)(WavData.GetData() + 22);
	int32 Rate = *(int32*)(WavData.GetData() + 24);
	int16 BitsPerSample = *(int16*)(WavData.GetData() + 34);
	int32 DataSize = *(int32*)(WavData.GetData() + 40);

	int32 SampleCount = DataSize / (BitsPerSample / 8);

	USoundWaveProcedural* SoundWave = NewObject<USoundWaveProcedural>();
	SoundWave->SetSampleRate(Rate);
	SoundWave->NumChannels = Channels;
	SoundWave->Duration = (float)SampleCount / (float)(Rate * Channels);

	// Convert to float samples and queue
	TArray<float> Samples;
	Samples.SetNum(SampleCount);

	const uint8* DataPtr = WavData.GetData() + 44;
	if (BitsPerSample == 16)
	{
		for (int32 i = 0; i < SampleCount; i++)
		{
			int16 Val = *(int16*)(DataPtr + i * 2);
			Samples[i] = (float)Val / 32768.0f;
		}
	}

	// Queue audio data
	SoundWave->QueueAudio(Samples.GetData(), Samples.Num() * sizeof(float));

	// Play via 2D sound
	UGameplayStatics::PlaySound2D(GetWorld(), SoundWave);

	// Broadcast completion (approximate - after duration)
	FTimerHandle TimerHandle;
	GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this]()
	{
		OnAudioPlayed.Broadcast();
	}, SoundWave->Duration + 0.1f, false);
}

FString USparkVoiceChat::GenerateSessionId() const
{
	return FGuid::NewGuid().ToString(EGuidFormats::DigitsLower);
}
