#include "FriendApplyManager.h"
#include "Log/LogChatSdk.h"
#include "Log/CsdkLogMacros.h"
#include "relationship/relationship_dto.pb.h"
#include "Tools/CsdkCommonHttp.h"
#include "Tools/CsdkCommonTools.h"
#include "User/Tool/UserTool.h"
#include "Async/TaskGraphInterfaces.h"
#include "Misc/FileHelper.h"
#include "Policies/CondensedJsonPrintPolicy.h"

using namespace heroim::client::v3beta::dto::relationship;

const FString RoleIdKey = TEXT("RoleId");
const FString ServerIdKey = TEXT("ServerId");
const FString TokenKey = TEXT("Token");

void FFriendApplyManager::Init()
{
	GetFriendApplyListRemote();
}

void FFriendApplyManager::Clear()
{
	bIsPull = false;
	UserId2ApplyMap.Empty();
	Token2UserIds.Empty();
}

void FFriendApplyManager::GetFriendApplyList(
	const TFunction<void(const int32 ErrorCode, const FString& ErrorMsg, const TArray<FCsdkFriendApply>& ApplyList)>&
	Callback)
{
	if( bIsPull)
	{
		AsyncTask(ENamedThreads::GameThread,[this,Callback]()
		{
			TArray<FCsdkFriendApply> Result;
			for(const auto& Pair:UserId2ApplyMap)
			{
				Result.Add(Pair.Value.Key);
			}
	
			Result.Sort([](const FCsdkFriendApply& A, const FCsdkFriendApply& B)
			{
				return A.ApplyTime > B.ApplyTime;
			});
			CSDK_LOG(LogChatSdk,Log,TEXT("GetFriendApplyList by Local Friend Apply"));
			Callback(ECsdkErrorCode::Success,TEXT("Local Friend Apply"),Result);
		});
		return;
	}
	GetFriendApplyListRemote(Callback);
}

void FFriendApplyManager::GetFriendApplyListRemote(
	const TFunction<void(const int32 ErrorCode, const FString& ErrorMsg, const TArray<FCsdkFriendApply>& ApplyList)>&
	Callback)
{
	FriendApplyListRequest Request;
	Request.set_allocated_base(FCsdkCommonTools::MakePbBase());
	
	auto RequestCallBack = [this,Callback](const int32 ErrorCode,const FString& ErrorMsg,const FriendApplyListResponse& Resp)
	{
		if(ErrorCode == ECsdkErrorCode::Success)
		{
			bIsPull = true;
		}
		CSDK_LOG(LogChatSdk,Log,TEXT("Get ApplyList:"));
		TArray<FCsdkFriendApply> ApplyList;
		for(const auto& ApplyItem : Resp.list())
		{
			FCsdkFriendApply FriendApply;
			FriendApply.User = CsdkUserTool::ConvertUser(ApplyItem.user());
			FriendApply.ApplyTime =  ApplyItem.applytime();
			FriendApply.Token = STD_STRING_TO_FSTRING(ApplyItem.acttoken());
			UserId2ApplyMap.Add(FriendApply.User.Id,MakeTuple(FriendApply,false));
			Token2UserIds.Add(FriendApply.Token,FriendApply.User.Id);
			ApplyList.Add(FriendApply);
		}

		LoadUnreadCountFile();
		if(Callback)
		{
			Callback(ErrorCode,ErrorMsg,ApplyList);
		}
	};
	
	FCsdkCommonHttp<FriendApplyListResponse>::Request(TEXT("/spi/v3beta/rs/friend/apply/list"),Request,RequestCallBack);
}

void FFriendApplyManager::AgreeFriendApply(const TArray<FString>& ApplyTokens,
	const TFunction<void(const int32 ErrorCode, const FString& ErrorMsg, const TArray<FCsdkOperateFriendApplyResult>&
	AgreeListResult)>& Callback)
{
	RSFriendApplyAgreeRequest Request;
	Request.set_allocated_base(FCsdkCommonTools::MakePbBase());
	for(const auto& ApplyToken :ApplyTokens)
	{
		Request.add_tokens(FSTRING_TO_STD_STRING(ApplyToken));
	}
	auto RequestCallBack = [this, Callback](const int32 ErrorCode,const FString& ErrorMsg,const RSFriendApplyAgreeResponse& Resp)
	{
		TArray<FCsdkOperateFriendApplyResult> RefuseResultsArray;
		TArray<FString> Tokens;
		for (const auto& ApplyItemResult : Resp.list())
		{
			FCsdkOperateFriendApplyResult Result;
			Result.ErrorCode = ApplyItemResult.code();
			Result.Msg = STD_STRING_TO_FSTRING(ApplyItemResult.message());
			Result.Token = STD_STRING_TO_FSTRING(ApplyItemResult.token());
			RefuseResultsArray.Add(Result);
			
			Tokens.Add(Result.Token);
		}

		RemoveFriendApply(Tokens);
		
		Callback(ErrorCode, ErrorMsg, RefuseResultsArray);
	};
	
	FCsdkCommonHttp<RSFriendApplyAgreeResponse>::Request(TEXT("/spi/v3beta/rs/friend/apply/agree"),Request,RequestCallBack);
}

void FFriendApplyManager::RefuseFriendApply(const TArray<FString>& ApplyTokens,
	const TFunction<void(const int32 ErrorCode, const FString& ErrorMsg, const TArray<FCsdkOperateFriendApplyResult>&
	RefuseListResult)>& Callback)
{
	RSFriendApplyDisagreeRequest Request;
	Request.set_allocated_base(FCsdkCommonTools::MakePbBase());
	for(const auto& ApplyToken :ApplyTokens)
	{
		Request.add_tokens(FSTRING_TO_STD_STRING(ApplyToken));
	}
	auto RequestCallBack = [this,Callback](const int32 ErrorCode,const FString& ErrorMsg,const RSFriendApplyDisagreeResponse& Resp)
	{
		TArray<FCsdkOperateFriendApplyResult> RefuseResultsArray;
		TArray<FString> Tokens;
		for (const auto& ApplyItemResult : Resp.list())
		{
			FCsdkOperateFriendApplyResult Result;
			Result.ErrorCode = ApplyItemResult.code();
			Result.Msg = STD_STRING_TO_FSTRING(ApplyItemResult.message());
			Result.Token = STD_STRING_TO_FSTRING(ApplyItemResult.token());
			RefuseResultsArray.Add(Result);
			Tokens.Add(Result.Token);
		}
		RemoveFriendApply(Tokens);
		Callback(ErrorCode, ErrorMsg, RefuseResultsArray);
	};
	
	FCsdkCommonHttp<RSFriendApplyDisagreeResponse>::Request(TEXT("/spi/v3beta/rs/friend/disagree"),Request,RequestCallBack);
}

void FFriendApplyManager::OnFriendApplied(const FString& JsonData)
{
	TSharedPtr<FJsonObject> RootObject = MakeShareable(new FJsonObject());
	const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonData);
	if (FJsonSerializer::Deserialize(Reader, RootObject))
	{
		const TSharedPtr<FJsonObject>* DataObj = nullptr;
		RootObject->TryGetObjectField(TEXT("fromUser"), DataObj);
		const FCsdkUser FromUser = CsdkUserTool::ConvertUser(*DataObj);
		FString ActToken;
		RootObject->TryGetStringField(TEXT("actToken"), ActToken);
		FCsdkFriendApply FriendApply;
		FriendApply.User = FromUser;
		FriendApply.ApplyTime = FDateTime::UtcNow();
		FriendApply.Token = ActToken;

		UpdateFriendApply(FriendApply);
		
		FCsdkManager::GetInstance().Subsystem->OnFriendApplied.Broadcast(FromUser,FriendApply);
		CSDK_LOG(LogChatSdk, Log, TEXT("On Friend Applied, From User : %s, ActToken : %s"), *FromUser.ToString(),*ActToken);
	}
}

int32 FFriendApplyManager::GetUnreadCount()
{
	int32 Result = 0;
	for (const auto& Pair : UserId2ApplyMap)
	{
		if(!Pair.Value.Value)
		{
			Result++;
		}
	}
	return Result;
}

void FFriendApplyManager::ClearUnreadCount()
{
	bool bNeedToStore = false;
	for (auto& Pair : UserId2ApplyMap)
	{
		if(!Pair.Value.Value) //存在未读数记录
		{
			bNeedToStore = true;
			Pair.Value.Value =true;
		}
	
	}
	if(bNeedToStore)
	{
		SaveApplyUsersToFile();
		FCsdkManager::GetInstance().Subsystem->OnFriendApplyUnreadCountChanged.Broadcast();
	}
}

void FFriendApplyManager::TryRemoveApply(const FCsdkUserId& FriendId)
{
	if(const auto FriendApply = UserId2ApplyMap.Find(FriendId))
	{
		Token2UserIds.Remove((*FriendApply).Key.Token);
		UserId2ApplyMap.Remove(FriendId);
		FCsdkManager::GetInstance().Subsystem->OnFriendApplyUnreadCountChanged.Broadcast();
	}

}

bool FFriendApplyManager::LoadUnreadCountFile()
{
	FString JsonStr;
	bool Result = FFileHelper::LoadFileToString(JsonStr, *GetSavePath());
	if(!Result)
	{
		CSDK_LOG(LogChatSdk,Log,TEXT("FFriendApplyCache Load Failed"));
		return Result;

	}

	TArray<TSharedPtr<FJsonValue>> JsonArray;
	const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonStr);

	Result =FJsonSerializer::Deserialize(Reader, JsonArray);
	if(!Result)
	{
		CSDK_LOG(LogChatSdk,Error,TEXT("FFriendApplyCache Error,Json Deserialize Error"));
		return Result;

	}

	for(const auto& JsonValue: JsonArray)
	{
		const auto& JsonObj = JsonValue->AsObject();
		if(!JsonObj.IsValid())
		{
			continue;
		}

		const FString RoleId = JsonObj->GetStringField(RoleIdKey);
		const FString ServerId = JsonObj->GetStringField(ServerIdKey);
		FString Token = JsonObj->GetStringField(TokenKey);

		if(const auto& Ptr = UserId2ApplyMap.Find({RoleId,ServerId}))
		{
			if((*Ptr).Key.Token == Token)
			{
				(*Ptr).Value = true;
			}
		}
	}
	return true;
}

bool FFriendApplyManager::SaveApplyUsersToFile()
{
	FString JsonStr;
	const TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> JsonWriter =
		TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&JsonStr);
	
	JsonWriter->WriteArrayStart();
	for(const auto& Item:UserId2ApplyMap )
	{
		JsonWriter->WriteObjectStart();
		JsonWriter->WriteValue(RoleIdKey,Item.Key.RoleId);
		JsonWriter->WriteValue(ServerIdKey,Item.Key.ServerId);
		JsonWriter->WriteValue(TokenKey,Item.Value.Key.Token);
		JsonWriter->WriteObjectEnd();
	}
	JsonWriter->WriteArrayEnd();
	JsonWriter->Close();
	return FFileHelper::SaveStringToFile(JsonStr,*GetSavePath());
}

const FString UnReadCountPath =TEXT("FriendApplyUnreadCount");

FString FFriendApplyManager::GetSavePath()
{
	return FPaths::Combine(FCsdkCommonTools::GetUserManager().GetCurrentUserSavePath(),UnReadCountPath+TEXT(".txt"));
}

void FFriendApplyManager::RemoveFriendApply(const TArray<FString>& InTokens)
{
	for(const auto& Token:InTokens)
	{
		if(const auto& IdPtr = Token2UserIds.Find(Token))
		{
			UserId2ApplyMap.Remove(*IdPtr);
		}
		Token2UserIds.Remove(Token);
	}
}

void FFriendApplyManager::UpdateFriendApply(const FCsdkFriendApply& FriendApply)
{
	if(const auto&  ApplyPairPtr = UserId2ApplyMap.Find(FriendApply.User.Id))
	{
		auto& ApplyPair =*ApplyPairPtr;
		Token2UserIds.Remove(ApplyPair.Key.Token);
		ApplyPair.Key = FriendApply;
		ApplyPair.Value = false;
		Token2UserIds.Add(FriendApply.Token,FriendApply.User.Id);
		FCsdkManager::GetInstance().Subsystem->OnFriendApplyUnreadCountChanged.Broadcast();
	}
	else
	{
		UserId2ApplyMap.Add(FriendApply.User.Id,MakeTuple( FriendApply,false));
		Token2UserIds.Add(FriendApply.Token,FriendApply.User.Id);
		FCsdkManager::GetInstance().Subsystem->OnFriendApplyUnreadCountChanged.Broadcast();
	}
}