#include "ZMMOAttributeNetworkHandler.h"

#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "GameFramework/GameStateBase.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerState.h"
#include "ZMMOAttributeComponent.h"
#include "ZMMOAttributeTypes.h"
#include "ZeusNetworkSubsystem.h"

namespace
{
	// Helper privado — converte o payload binario do plugin pra USTRUCT
	// Blueprint que o componente exibe.
	FZMMOAttributesSnapshot ToSnapshot(const FZeusAttributesPayload& P)
	{
		FZMMOAttributesSnapshot S;
		S.EntityId    = P.EntityId;
		S.ClassId     = P.ClassId;
		S.BaseLevel   = P.BaseLevel;
		S.BaseExp     = P.BaseExp;
		S.JobLevel    = P.JobLevel;
		S.JobExp      = P.JobExp;
		S.Str         = P.Str;
		S.Agi         = P.Agi;
		S.Vit         = P.Vit;
		S.Int         = P.Int;
		S.Dex         = P.Dex;
		S.Luk         = P.Luk;
		S.StatusPoint = P.StatusPoint;
		S.SkillPoint  = P.SkillPoint;
		S.Hp          = P.Hp;
		S.MaxHp       = P.MaxHp;
		S.Sp          = P.Sp;
		S.MaxSp       = P.MaxSp;
		S.Money       = P.Money;
		S.AtkBase            = P.AtkBase;
		S.AtkEquipBonus      = P.AtkEquipBonus;
		S.MatkBase           = P.MatkBase;
		S.MatkEquipBonus     = P.MatkEquipBonus;
		S.DefBase            = P.DefBase;
		S.DefEquipBonus      = P.DefEquipBonus;
		S.MdefBase           = P.MdefBase;
		S.MdefEquipBonus     = P.MdefEquipBonus;
		S.HitBase            = P.HitBase;
		S.HitEquipBonus      = P.HitEquipBonus;
		S.FleeBase           = P.FleeBase;
		S.FleeEquipBonus     = P.FleeEquipBonus;
		S.CritBaseX10        = P.CritBaseX10;
		S.CritEquipBonusX10  = P.CritEquipBonusX10;
		S.Aspd               = P.Aspd;
		return S;
	}

	// Resolve `EntityId -> UZMMOAttributeComponent` via PlayerArray do GameState.
	// AttributeComponent agora vive no PlayerState (vide AZMMOPlayerState +
	// Component Registry em DefaultGame.ini). Itera apenas players (1 por
	// conexao), nao 1000+ atores — O(N_players).
	//
	// Cada UZMMOAttributeComponent carrega seu proprio EntityId (seed em
	// AZMMOPlayerCharacter::HandleLocalSpawnReady ou no primeiro snapshot).
	UZMMOAttributeComponent* FindComponentForEntity(UWorld* World, int32 EntityId)
	{
		if (!World || EntityId == 0) { return nullptr; }
		const AGameStateBase* GS = World->GetGameState();
		if (!GS) { return nullptr; }
		for (APlayerState* PS : GS->PlayerArray)
		{
			if (!PS) { continue; }
			UZMMOAttributeComponent* Comp = PS->FindComponentByClass<UZMMOAttributeComponent>();
			if (!Comp) { continue; }
			if (Comp->GetSnapshot().EntityId == EntityId)
			{
				return Comp;
			}
		}
		return nullptr;
	}
}

bool UZMMOAttributeNetworkHandler::ShouldCreateSubsystem(UObject* Outer) const
{
	// Cria apenas para mundos de gameplay (PIE/Game), nao para editor preview.
	UWorld* World = Cast<UWorld>(Outer);
	if (!World) { return false; }
	return World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE;
}

void UZMMOAttributeNetworkHandler::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
	{
		SnapshotFullHandle = Net->OnAttributeSnapshotFull.AddUObject(
			this, &UZMMOAttributeNetworkHandler::HandleAttributeSnapshotFull);
		HpSpUpdateHandle = Net->OnHpSpUpdate.AddUObject(
			this, &UZMMOAttributeNetworkHandler::HandleHpSpUpdate);
		LevelUpHandle = Net->OnLevelUp.AddUObject(
			this, &UZMMOAttributeNetworkHandler::HandleLevelUp);
		StatAllocReplyHandle = Net->OnStatAllocReply.AddUObject(
			this, &UZMMOAttributeNetworkHandler::HandleStatAllocReply);
	}
}

void UZMMOAttributeNetworkHandler::Deinitialize()
{
	if (UZeusNetworkSubsystem* Net = GetZeusNetSubsystem())
	{
		if (SnapshotFullHandle.IsValid())
		{
			Net->OnAttributeSnapshotFull.Remove(SnapshotFullHandle);
			SnapshotFullHandle.Reset();
		}
		if (HpSpUpdateHandle.IsValid())
		{
			Net->OnHpSpUpdate.Remove(HpSpUpdateHandle);
			HpSpUpdateHandle.Reset();
		}
		if (LevelUpHandle.IsValid())
		{
			Net->OnLevelUp.Remove(LevelUpHandle);
			LevelUpHandle.Reset();
		}
		if (StatAllocReplyHandle.IsValid())
		{
			Net->OnStatAllocReply.Remove(StatAllocReplyHandle);
			StatAllocReplyHandle.Reset();
		}
	}
	Super::Deinitialize();
}

UZeusNetworkSubsystem* UZMMOAttributeNetworkHandler::GetZeusNetSubsystem() const
{
	const UWorld* World = GetWorld();
	if (!World) { return nullptr; }
	UGameInstance* GI = World->GetGameInstance();
	if (!GI) { return nullptr; }
	return GI->GetSubsystem<UZeusNetworkSubsystem>();
}

void UZMMOAttributeNetworkHandler::HandleAttributeSnapshotFull(const FZeusAttributesPayload& Payload)
{
	UWorld* World = GetWorld();
	if (!World) { return; }
	UZMMOAttributeComponent* Comp = FindComponentForEntity(World, Payload.EntityId);
	if (!Comp)
	{
		// Fallback chicken-and-egg: primeiro snapshot chega antes do EntityId
		// estar seeded no componente. Aplica ao primeiro componente do
		// PlayerArray que ainda tem EntityId=0. Apos o primeiro apply, o
		// caminho normal por lookup funciona.
		const AGameStateBase* GS = World->GetGameState();
		if (GS)
		{
			for (APlayerState* PS : GS->PlayerArray)
			{
				if (!PS) { continue; }
				UZMMOAttributeComponent* Candidate = PS->FindComponentByClass<UZMMOAttributeComponent>();
				if (Candidate && Candidate->GetSnapshot().EntityId == 0)
				{
					Comp = Candidate;
					break;
				}
			}
		}
	}
	if (Comp)
	{
		Comp->ApplySnapshot(ToSnapshot(Payload));
	}
}

void UZMMOAttributeNetworkHandler::HandleHpSpUpdate(int32 EntityId, int32 Hp, int32 Sp)
{
	UWorld* World = GetWorld();
	if (!World) { return; }
	UZMMOAttributeComponent* Comp = FindComponentForEntity(World, EntityId);
	if (Comp)
	{
		Comp->ApplyHpSpUpdate(Hp, Sp);
	}
}

void UZMMOAttributeNetworkHandler::HandleLevelUp(int32 EntityId, int32 NewBaseLevel, int32 StatusPointDelta)
{
	UWorld* World = GetWorld();
	if (!World) { return; }
	UZMMOAttributeComponent* Comp = FindComponentForEntity(World, EntityId);
	if (Comp)
	{
		Comp->NotifyLevelUp(NewBaseLevel, StatusPointDelta);
	}
}

void UZMMOAttributeNetworkHandler::HandleStatAllocReply(bool bAccepted, int32 Reason)
{
	// S_ATTRIBUTE_STAT_ALLOC_OK nao traz EntityId no payload — sempre do
	// player local que fez o request. Busca o AttributeComponent via PC.
	if (UZMMOAttributeComponent* Comp = FindLocalPlayerAttributeComponent())
	{
		Comp->NotifyStatAllocReply(bAccepted, Reason);
	}
}

UZMMOAttributeComponent* UZMMOAttributeNetworkHandler::FindLocalPlayerAttributeComponent() const
{
	UWorld* World = GetWorld();
	if (!World) { return nullptr; }
	APlayerController* PC = World->GetFirstPlayerController();
	if (!PC || !PC->PlayerState) { return nullptr; }
	return PC->PlayerState->FindComponentByClass<UZMMOAttributeComponent>();
}
