#include "ZMMOWorldSubsystem.h"

#include "Engine/GameInstance.h"
#include "Engine/World.h"
#include "ZMMO.h"
#include "ZMMOEntity.h"
#include "ZMMOEntityInterface.h"
#include "ZMMOPlayerProxy.h"
#include "ZeusNetworkSubsystem.h"

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

	if (RemoteEntityClasses.IsEmpty())
	{
		RemoteEntityClasses.Add(EZMMOEntityType::Player, AZMMOPlayerProxy::StaticClass());
		RemoteEntityClasses.Add(EZMMOEntityType::Mob, AZMMOEntity::StaticClass());
		RemoteEntityClasses.Add(EZMMOEntityType::NPC, AZMMOEntity::StaticClass());
		RemoteEntityClasses.Add(EZMMOEntityType::Object, AZMMOEntity::StaticClass());
	}
	// Bind + replay vivem em OnWorldBeginPlay (mundo pronto). Initialize roda
	// MUITO cedo no pipeline de LoadMap — antes do GameMode ser instanciado
	// e antes do WorldPartition terminar de carregar cells. Spawns dinamicos
	// feitos aqui podem ser perdidos durante a fase de setup posterior.
}

void UZMMOWorldSubsystem::OnWorldBeginPlay(UWorld& InWorld)
{
	Super::OnWorldBeginPlay(InWorld);

	if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
	{
		ZeusNet->OnPlayerSpawned.AddDynamic(this, &UZMMOWorldSubsystem::HandlePlayerSpawned);
		ZeusNet->OnPlayerDespawned.AddDynamic(this, &UZMMOWorldSubsystem::HandlePlayerDespawned);
		ZeusNet->OnPlayerStateUpdate.AddDynamic(this, &UZMMOWorldSubsystem::HandlePlayerStateUpdate);
		UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem bound to ZeusNetworkSubsystem delegates."));

		// Catch-up race fix (Fase 3): broadcasts de proxies remotos chegam
		// enquanto o cliente novo ainda esta em OpenLevel. UZeusNetworkSubsystem
		// cacheia em PendingRemoteSpawnsByEntity. Aplicamos aqui — mundo ja
		// esta pronto (GameMode ativo, WorldPartition cells inicializadas).
		int32 ReplayCount = 0;
		ZeusNet->ForEachPendingRemoteSpawn(
			[this, &ReplayCount](const int32 EntityId, const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
			{
				HandlePlayerSpawned(EntityId, /*bIsLocal=*/false, PosCm, YawDeg, ServerTimeMs);
				++ReplayCount;
			});
		if (ReplayCount > 0)
		{
			UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: replay de %d proxy(ies) cacheados."), ReplayCount);
		}
	}
	else
	{
		UE_LOG(LogZMMO, Warning, TEXT("ZMMOWorldSubsystem: ZeusNetworkSubsystem not found at OnWorldBeginPlay."));
	}
}

void UZMMOWorldSubsystem::Deinitialize()
{
	if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
	{
		ZeusNet->OnPlayerSpawned.RemoveDynamic(this, &UZMMOWorldSubsystem::HandlePlayerSpawned);
		ZeusNet->OnPlayerDespawned.RemoveDynamic(this, &UZMMOWorldSubsystem::HandlePlayerDespawned);
		ZeusNet->OnPlayerStateUpdate.RemoveDynamic(this, &UZMMOWorldSubsystem::HandlePlayerStateUpdate);
	}

	RemoteEntities.Reset();
	LocalEntityId = 0;
	Super::Deinitialize();
}

void UZMMOWorldSubsystem::RegisterLocalEntity(const int64 EntityId, AActor* LocalActor)
{
	if (EntityId == 0 || LocalActor == nullptr)
	{
		return;
	}

	LocalEntityId = EntityId;
	RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(LocalActor));
	UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: local entity registered EntityId=%lld actor=%s"),
		EntityId, *GetNameSafe(LocalActor));
}

void UZMMOWorldSubsystem::HandlePlayerSpawned(const int32 EntityId, const bool bIsLocal,
	const FVector PosCm, const float YawDeg, const int64 ServerTimeMs)
{
	if (EntityId == 0)
	{
		return;
	}

	if (bIsLocal)
	{
		// O proprio AZMMOPlayerCharacter cuida da sua identidade quando recebe o
		// delegate por outro caminho. Aqui apenas memorizamos para filtrar
		// snapshots locais (cliente solto, sem reconciliacao em V0).
		LocalEntityId = EntityId;
		return;
	}

	if (RemoteEntities.Contains(EntityId))
	{
		// Ja existe um proxy: reactivar e re-snap.
		if (AActor* Existing = RemoteEntities[EntityId].Get())
		{
			if (IZMMOEntityInterface* AsEntity = Cast<IZMMOEntityInterface>(Existing))
			{
				AsEntity->SetEntityRelevant(true);
			}
			Existing->SetActorLocationAndRotation(PosCm, FRotator(0.0f, YawDeg, 0.0f));
		}
		return;
	}

	UWorld* World = GetWorld();
	if (!World)
	{
		return;
	}

	UClass* ProxyClass = ResolveActorClass(EZMMOEntityType::Player);
	if (!ProxyClass)
	{
		UE_LOG(LogZMMO, Warning, TEXT("ZMMOWorldSubsystem: no class registered for Player; spawn aborted."));
		return;
	}

	FActorSpawnParameters Params;
	Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
	const FRotator SpawnRot(0.0f, YawDeg, 0.0f);
	AActor* SpawnedActor = World->SpawnActor<AActor>(ProxyClass, PosCm, SpawnRot, Params);
	if (!SpawnedActor)
	{
		UE_LOG(LogZMMO, Warning, TEXT("ZMMOWorldSubsystem: SpawnActor failed for EntityId=%d"), EntityId);
		return;
	}

	if (IZMMOEntityInterface* AsEntity = Cast<IZMMOEntityInterface>(SpawnedActor))
	{
		// Inject identity via dedicated setter when the spawned actor exposes one.
		if (AZMMOPlayerProxy* Proxy = Cast<AZMMOPlayerProxy>(SpawnedActor))
		{
			Proxy->SetZMMOIdentity(EntityId, EZMMOEntityType::Player);
		}
		else if (AZMMOEntity* Entity = Cast<AZMMOEntity>(SpawnedActor))
		{
			Entity->SetZMMOIdentity(EntityId, EZMMOEntityType::Player);
		}
		AsEntity->SetEntityRelevant(true);
	}

	RemoteEntities.Add(EntityId, TWeakObjectPtr<AActor>(SpawnedActor));
	UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: spawned remote EntityId=%d at (%s) yaw=%.1f t=%lld"),
		EntityId, *PosCm.ToString(), YawDeg, ServerTimeMs);
}

void UZMMOWorldSubsystem::HandlePlayerDespawned(const int32 EntityId)
{
	const TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId);
	if (!Entry)
	{
		return;
	}

	if (AActor* Actor = Entry->Get())
	{
		if (IZMMOEntityInterface* AsEntity = Cast<IZMMOEntityInterface>(Actor))
		{
			AsEntity->SetEntityRelevant(false);
		}
	}

	UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: despawn EntityId=%d"), EntityId);
}

void UZMMOWorldSubsystem::HandlePlayerStateUpdate(const int32 EntityId, const int32 InputSeq,
	const FVector PosCm, const FVector VelCmS, const bool bGrounded, const int64 ServerTimeMs)
{
	if (EntityId == 0)
	{
		return;
	}

	if (LocalEntityId != 0 && static_cast<int64>(EntityId) == LocalEntityId)
	{
		// Cliente local solto: ignoramos snapshots autoritativos para o nosso
		// proprio personagem em V0 (ADR 0038). Reconciliacao opcional pode ser
		// activada por flag em `AZMMOPlayerCharacter` quando colisao de objetos
		// chegar.
		return;
	}

	const TWeakObjectPtr<AActor>* Entry = RemoteEntities.Find(EntityId);
	if (!Entry)
	{
		// Snapshot antes de spawn: o spawn vira em `OnPlayerSpawned` em breve.
		return;
	}

	AActor* Actor = Entry->Get();
	if (!Actor)
	{
		RemoteEntities.Remove(EntityId);
		return;
	}

	IZMMOEntityInterface* AsEntity = Cast<IZMMOEntityInterface>(Actor);
	if (!AsEntity)
	{
		return;
	}

	FZMMOEntitySnapshot Snapshot;
	Snapshot.EntityId = EntityId;
	Snapshot.EntityType = AsEntity->GetZMMOEntityType();
	Snapshot.LastProcessedInputSeq = InputSeq;
	Snapshot.PositionCm = PosCm;
	Snapshot.VelocityCmS = VelCmS;

	// Etapa 7 (server-side networking refactor): yaw agora chega no
	// snapshot quantizado v3. Buscamos via TryGetLastPlayerStateExtended
	// (cache local do plugin). Se PSF_HasYaw nao estiver set ou se o cache
	// nao tiver entrada, caimos no fallback antigo (derivar do XY da
	// velocidade ou manter rotacao do ator).
	bool bYawFromSnapshot = false;
	if (UZeusNetworkSubsystem* ZeusNet = ResolveZeusNetworkSubsystem())
	{
		int32 CachedInputSeq = 0;
		FVector CachedPos = FVector::ZeroVector;
		FVector CachedVel = FVector::ZeroVector;
		float CachedYawDeg = 0.f;
		int32 CachedFlags = 0;
		int64 CachedServerTimeMs = 0;
		if (ZeusNet->TryGetLastPlayerStateExtended(
				EntityId, CachedInputSeq, CachedPos, CachedVel,
				CachedYawDeg, CachedFlags, CachedServerTimeMs))
		{
			constexpr int32 PSF_HasYaw = 1 << 5;
			if ((CachedFlags & PSF_HasYaw) != 0)
			{
				Snapshot.YawDeg = CachedYawDeg;
				bYawFromSnapshot = true;
			}
		}
	}
	if (!bYawFromSnapshot)
	{
		// Fallback: deriva yaw do XY da velocidade quando significativo;
		// caso contrario mantem rotacao do ator (comportamento pre-Etapa 7).
		if (!FVector(VelCmS.X, VelCmS.Y, 0.0f).IsNearlyZero(1.0f))
		{
			Snapshot.YawDeg = FMath::RadiansToDegrees(FMath::Atan2(VelCmS.Y, VelCmS.X));
		}
		else
		{
			Snapshot.YawDeg = Actor->GetActorRotation().Yaw;
		}
	}
	Snapshot.bGrounded = bGrounded;
	Snapshot.ServerTimeMs = ServerTimeMs;

	AsEntity->ApplyEntitySnapshot(Snapshot);
}

UClass* UZMMOWorldSubsystem::ResolveActorClass(const EZMMOEntityType EntityType) const
{
	if (const TSubclassOf<AActor>* Found = RemoteEntityClasses.Find(EntityType))
	{
		if (Found->Get())
		{
			return Found->Get();
		}
	}
	return AZMMOEntity::StaticClass();
}

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