
#pragma once

#include "CoreMinimal.h"
#include "PortaSubsystem.h"
#include "SocketIoClient.h"
#include "PortaUtils.h"
#include "Runtime/Online/HTTP/Public/Http.h"
#include "Backends/JsonStructSerializerBackend.h"
#include "Subsystems/EngineSubsystem.h"
#include "TimerManager.h"
#include "PortaConnectionSubsystem.generated.h"

typedef TMap<FString, FString> FPortaUrlParamMap;

//DECLARE_DELEGATE_TwoParams(FPortaRouteHandler, const FPortaUrlParamMap&, TArray<uint8>&);
DECLARE_DELEGATE_RetVal_FourParams(TSharedPtr<FJsonObject>, FPortaRouteHandler, const FPortaUrlParamMap&, const FString&, const FString&, const FString&);

class UPortaConnectionSubsystem;

struct FPortaRoute
{
	FPortaRoute() = default;
	FPortaRoute(const FString& Url, const FString& Verb, FPortaRouteHandler Handler) :
		Url(Url),
		Verb(Verb),
		Handler(Handler)
	{
		Url.ParseIntoArray(UrlSplit, TEXT("/"), 1);
	}

	bool Matches(const FString& MsgUrl, const FString& MsgVerb, TMap<FString, FString>& OutParams);

	FString Url;
	FString Verb;
	FPortaRouteHandler Handler;
	TArray<FString> UrlSplit;
};

UCLASS()
class PORTAINTERFACE_API UPortaAsyncCallHolder : public UObject
{
	GENERATED_BODY()
public:
	UFUNCTION()
	void Callback();

	virtual void ProcessEvent(UFunction* Function, void* Parms) override;

	UPROPERTY()
	UPortaConnectionSubsystem* Subsystem;

	UPROPERTY()
	FString Sender;

	UPROPERTY()
	FString ID;

	UPROPERTY()
	UFunction* DelegateSignature;

	bool Subscription = false;
};

UCLASS()
class PORTAINTERFACE_API UPortaHeartbeat : public UObject, public FTickableGameObject
{
	GENERATED_BODY()

public:
	virtual void Tick(float DeltaTime) override;

	virtual bool IsTickable() const override;

	virtual TStatId GetStatId() const override;
	virtual bool IsTickableInEditor() const override { return true; }

private:
	float TimeUntilHeartbeat = 0.f;
};

UCLASS()
class PORTAINTERFACE_API UPortaConnectionSubsystem : public UPortaSubsystem
{
	GENERATED_BODY()
public:
	friend class FPortaConnectionSubsystemObjectCallTest;

	static UPortaConnectionSubsystem* Get() { return PortaUtils::GetEngineSubsystem<UPortaConnectionSubsystem>(); }

	virtual void Init() override;

	virtual void Start() override;
	virtual void Stop() override;

	void SendAsyncResponse(UPortaAsyncCallHolder* Async, void* Args);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void GetComboItems(const FString& PresetName, const FString& Field, const FString& Getter, TArray<FString>& OutLabels, TArray<FString>& OutValues);

	UFUNCTION(BlueprintCallable, Category = "Porta")
	static void SetPresetPropertyFromJson(const FString& PresetName, const FString& PropertyName, const FString& ValueJsonStr);

	static TSharedPtr<FJsonObject> SetPresetProperty(const FString& PresetName, const FString& PropertyName, const FString& ValueJsonStr);
	static TSharedPtr<FJsonObject> CallPresetFunction(const FString& PresetName, const FString& FuncName, TSharedPtr<FJsonObject> Params);

	UPROPERTY(EditAnywhere, Category = "Porta")
	FString PortaChannel;

	TSharedPtr<FJsonObject> RunBatch(const FString& Content);
	static FString FixupPresetName(const FString& PresetName, bool* IsAva = nullptr, FString* Channel = nullptr, FString* AssetPath = nullptr);

	void Heartbeat();

	

private:
	void OnConnected();
	void HandleMessage(const TSharedPtr<FJsonValue>& Message);
	void DoOldHttpMessage(const TSharedPtr<FJsonValue>& Message);
	void OnHttpResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, const FString& MessageID, const FString& Sender);
	void OnEventHttpResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful);
	void SendResponseMessage(const FString& Channel, const FString& MessageID, const TSharedPtr<FJsonObject>& Response);

	void RegisterRoute(const FString& Url, const FString& Verb, FPortaRouteHandler Handler);

	TSharedPtr<FJsonObject> HandleGetPresetsRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);
	TSharedPtr<FJsonObject> HandleGetPresetRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);
	TSharedPtr<FJsonObject> HandleGetPresetPropertyRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);
	TSharedPtr<FJsonObject> HandleSetPresetPropertyRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);
	TSharedPtr<FJsonObject> HandleSetPresetControllerRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);
	TSharedPtr<FJsonObject> HandleCallPresetFunctionRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);
	TSharedPtr<FJsonObject> HandleObjectCallRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);
	TSharedPtr<FJsonObject> HandleSearchAssetsRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);
	TSharedPtr<FJsonObject> HandleBatchRoute(const TMap<FString, FString>& UrlParams, const FString& Content, const FString& Sender, const FString& MsgID);

	UPROPERTY(Transient)
	UPortaHeartbeat* HeartbeatHolder;

	UPROPERTY()
	TSet<UPortaAsyncCallHolder*> AsyncCalls;

	UPROPERTY(Transient)
	USocketIoClient* SocketIoClient;
	TArray<TUniquePtr<FPortaRoute>> Routes;
	FTimerHandle Timer;

	static const FString Namespace;
};
