// Copyright Pacificao. All Rights Reserved.

#include "AgrarianSurvivalComponent.h"
#include "AgrarianGameState.h"
#include "AgrarianPerformanceStats.h"
#include "AgrarianShelterActor.h"
#include "AgrarianWeatherExposureZone.h"
#include "Components/BoxComponent.h"
#include "Engine/World.h"
#include "Net/UnrealNetwork.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"

UAgrarianSurvivalComponent::UAgrarianSurvivalComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
	SetIsReplicatedByDefault(true);
}

void UAgrarianSurvivalComponent::BeginPlay()
{
	Super::BeginPlay();
	ClampSurvival();
	ClampCareHistory();
	BroadcastSurvivalChanged();
}

void UAgrarianSurvivalComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	SCOPE_CYCLE_COUNTER(STAT_AgrarianSurvivalTick);
	TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianSurvivalTick);

	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (!GetOwner() || !GetOwner()->HasAuthority() || !IsAlive())
	{
		return;
	}

	const float Minutes = DeltaTime / 60.0f;
	Survival.Hunger -= HungerDecayPerMinute * Minutes;
	Survival.Thirst -= ThirstDecayPerMinute * Minutes;
	Survival.Stamina += StaminaRecoveryPerSecond * DeltaTime;
	CurrentWeatherProtection = CalculateCurrentWeatherProtection();
	CurrentWeatherExposureMultiplier = CalculateCurrentWeatherExposureMultiplier();
	CurrentWeatherTemperatureOffsetC = CalculateCurrentWeatherTemperatureOffsetC();
	CareHistory.ShelterQuality = FMath::FInterpTo(CareHistory.ShelterQuality, CurrentWeatherProtection, DeltaTime, 0.02f);

	if (Survival.Stamina <= LowStaminaExhaustionThreshold)
	{
		Survival.Exhaustion += ExhaustionGainPerLowStaminaSecond * DeltaTime;
	}
	else if (Survival.Hunger > 10.0f && Survival.Thirst > 10.0f && Survival.BodyTemperature >= 35.0f)
	{
		Survival.Exhaustion -= ExhaustionRecoveryPerSecond * DeltaTime;
	}

	if (Survival.SicknessSeverity > 0.0f)
	{
		Survival.Exhaustion += (Survival.SicknessSeverity / 100.0f) * 0.08f * DeltaTime;
		CareHistory.IllnessBurden += (Survival.SicknessSeverity / 100.0f) * 0.001f * DeltaTime;

		if (Survival.Hunger > 20.0f && Survival.Thirst > 20.0f && Survival.BodyTemperature >= 35.0f)
		{
			Survival.SicknessSeverity -= SicknessRecoveryPerSecond * DeltaTime * FMath::Max(0.25f, CareHistory.TreatmentQuality);
		}
	}

	if (Survival.BleedingSeverity > 0.0f)
	{
		const float BleedingRatio = Survival.BleedingSeverity / 100.0f;
		Survival.Health -= BleedingDamagePerMinute * BleedingRatio * Minutes;
		Survival.Exhaustion += BleedingExhaustionPerSecond * BleedingRatio * DeltaTime;
		CareHistory.InjuryBurden += BleedingRatio * 0.001f * DeltaTime;
	}

	if (Survival.SprainSeverity > 0.0f)
	{
		const float SprainRatio = Survival.SprainSeverity / 100.0f;
		Survival.Exhaustion += SprainExhaustionPerSecond * SprainRatio * DeltaTime;
		CareHistory.InjuryBurden += SprainRatio * 0.0005f * DeltaTime;
	}

	if (const UWorld* World = GetWorld())
	{
		if (const AAgrarianGameState* AgrarianGameState = World->GetGameState<AAgrarianGameState>())
		{
			const float ExposureProtectionMultiplier = 1.0f - FMath::Clamp(CurrentWeatherProtection, 0.0f, 1.0f);
			const float EffectiveAmbientTemperatureC = AgrarianGameState->AmbientTemperatureC + CurrentWeatherTemperatureOffsetC;
			const float ExposureDelta = (EffectiveAmbientTemperatureC - 18.0f) * 0.002f * DeltaTime * ExposureProtectionMultiplier * CurrentWeatherExposureMultiplier;
			Survival.BodyTemperature += FMath::Clamp(ExposureDelta, -0.035f, 0.02f);
		}
	}

	if (Survival.Hunger <= 0.0f)
	{
		Survival.Health -= StarvationDamagePerMinute * Minutes;
	}

	if (Survival.Thirst <= 0.0f)
	{
		Survival.Health -= DehydrationDamagePerMinute * Minutes;
	}

	if (Survival.BodyTemperature < 35.0f)
	{
		Survival.Health -= ColdDamagePerMinute * Minutes * (1.0f - FMath::Clamp(CurrentWeatherProtection, 0.0f, 1.0f)) * CurrentWeatherExposureMultiplier;
	}

	if (Survival.SicknessSeverity >= 60.0f)
	{
		Survival.Health -= SicknessDamagePerMinute * (Survival.SicknessSeverity / 100.0f) * Minutes;
	}

	ClampSurvival();
	ClampCareHistory();
	BroadcastSurvivalChanged();
}

void UAgrarianSurvivalComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(UAgrarianSurvivalComponent, Survival);
	DOREPLIFETIME(UAgrarianSurvivalComponent, CareHistory);
	DOREPLIFETIME(UAgrarianSurvivalComponent, CurrentWeatherProtection);
	DOREPLIFETIME(UAgrarianSurvivalComponent, CurrentWeatherExposureMultiplier);
	DOREPLIFETIME(UAgrarianSurvivalComponent, CurrentWeatherTemperatureOffsetC);
}

bool UAgrarianSurvivalComponent::IsAlive() const
{
	return !Survival.bIsDead && Survival.Health > 0.0f;
}

bool UAgrarianSurvivalComponent::IsDead() const
{
	return Survival.bIsDead || Survival.Health <= 0.0f;
}

void UAgrarianSurvivalComponent::ApplyDamage(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.Health -= FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::RestoreHealth(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		if (Survival.bIsDead)
		{
			return;
		}

		Survival.Health += FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::MarkDead(FName Reason)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.Health = 0.0f;
		Survival.Stamina = 0.0f;
		Survival.bIsDead = true;
		Survival.LastDeathReason = Reason.IsNone() ? FName(TEXT("unknown")) : Reason;
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::Revive(float HealthAmount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.bIsDead = false;
		Survival.LastDeathReason = NAME_None;
		Survival.Health = FMath::Clamp(HealthAmount, 1.0f, 100.0f);
		Survival.Stamina = FMath::Max(Survival.Stamina, 25.0f);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::AddFood(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.Hunger += FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::AddWater(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.Thirst += FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::AddWarmth(float DegreesCelsius)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.BodyTemperature += DegreesCelsius;
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::AddInjury(float Severity)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		const float PositiveSeverity = FMath::Max(0.0f, Severity);
		Survival.InjurySeverity += PositiveSeverity;
		Survival.BleedingSeverity += PositiveSeverity * 0.35f;
		Survival.SprainSeverity += PositiveSeverity * 0.20f;
		CareHistory.InjuryBurden += PositiveSeverity / 100.0f;
		Survival.Health -= PositiveSeverity * 5.0f;
		ClampSurvival();
		ClampCareHistory();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::ReduceInjury(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.InjurySeverity -= FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::AddBleeding(float Severity)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		const float PositiveSeverity = FMath::Max(0.0f, Severity);
		Survival.BleedingSeverity += PositiveSeverity;
		CareHistory.InjuryBurden += PositiveSeverity / 200.0f;
		ClampSurvival();
		ClampCareHistory();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::ReduceBleeding(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.BleedingSeverity -= FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::AddSprain(float Severity)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		const float PositiveSeverity = FMath::Max(0.0f, Severity);
		Survival.SprainSeverity += PositiveSeverity;
		CareHistory.InjuryBurden += PositiveSeverity / 200.0f;
		ClampSurvival();
		ClampCareHistory();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::ReduceSprain(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.SprainSeverity -= FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::AddSickness(float Severity)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		const float PositiveSeverity = FMath::Max(0.0f, Severity);
		Survival.SicknessSeverity += PositiveSeverity;
		CareHistory.IllnessBurden += PositiveSeverity / 100.0f;
		ClampSurvival();
		ClampCareHistory();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::ReduceSickness(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.SicknessSeverity -= FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::ApplySavedState(const FAgrarianSurvivalSnapshot& SavedSurvival, const FAgrarianCareHistorySnapshot& SavedCareHistory)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival = SavedSurvival;
		CareHistory = SavedCareHistory;
		ClampSurvival();
		ClampCareHistory();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::SpendStamina(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		const float PositiveAmount = FMath::Max(0.0f, Amount);
		Survival.Stamina -= PositiveAmount;
		Survival.Exhaustion += PositiveAmount * 0.05f;
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::AddExhaustion(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.Exhaustion += FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

void UAgrarianSurvivalComponent::ReduceExhaustion(float Amount)
{
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		Survival.Exhaustion -= FMath::Max(0.0f, Amount);
		ClampSurvival();
		BroadcastSurvivalChanged();
	}
}

float UAgrarianSurvivalComponent::CalculateCurrentWeatherProtection() const
{
	const AActor* Owner = GetOwner();
	if (!Owner)
	{
		return 0.0f;
	}

	TArray<AActor*> OverlappingShelterActors;
	Owner->GetOverlappingActors(OverlappingShelterActors, AAgrarianShelterActor::StaticClass());

	float BestProtection = 0.0f;
	for (const AActor* Actor : OverlappingShelterActors)
	{
		const AAgrarianShelterActor* Shelter = Cast<AAgrarianShelterActor>(Actor);
		if (!Shelter || !Shelter->ProtectionVolume || !Shelter->ProtectionVolume->IsOverlappingActor(Owner))
		{
			continue;
		}

		BestProtection = FMath::Max(BestProtection, FMath::Clamp(Shelter->WeatherProtection, 0.0f, 1.0f));
	}

	return BestProtection;
}

float UAgrarianSurvivalComponent::CalculateCurrentWeatherExposureMultiplier() const
{
	const AActor* Owner = GetOwner();
	if (!Owner)
	{
		return 1.0f;
	}

	TArray<AActor*> OverlappingZoneActors;
	Owner->GetOverlappingActors(OverlappingZoneActors, AAgrarianWeatherExposureZone::StaticClass());

	float StrongestMultiplierDelta = 0.0f;
	for (const AActor* Actor : OverlappingZoneActors)
	{
		const AAgrarianWeatherExposureZone* Zone = Cast<AAgrarianWeatherExposureZone>(Actor);
		if (!Zone || !Zone->ExposureVolume || !Zone->ExposureVolume->IsOverlappingActor(Owner))
		{
			continue;
		}

		const float ZoneDelta = FMath::Clamp(Zone->ExposureMultiplier, 0.0f, 3.0f) - 1.0f;
		if (FMath::Abs(ZoneDelta) > FMath::Abs(StrongestMultiplierDelta))
		{
			StrongestMultiplierDelta = ZoneDelta;
		}
	}

	return FMath::Clamp(1.0f + StrongestMultiplierDelta, 0.0f, 3.0f);
}

float UAgrarianSurvivalComponent::CalculateCurrentWeatherTemperatureOffsetC() const
{
	const AActor* Owner = GetOwner();
	if (!Owner)
	{
		return 0.0f;
	}

	TArray<AActor*> OverlappingZoneActors;
	Owner->GetOverlappingActors(OverlappingZoneActors, AAgrarianWeatherExposureZone::StaticClass());

	float StrongestOffset = 0.0f;
	for (const AActor* Actor : OverlappingZoneActors)
	{
		const AAgrarianWeatherExposureZone* Zone = Cast<AAgrarianWeatherExposureZone>(Actor);
		if (!Zone || !Zone->ExposureVolume || !Zone->ExposureVolume->IsOverlappingActor(Owner))
		{
			continue;
		}

		const float ZoneOffset = FMath::Clamp(Zone->TemperatureOffsetC, -20.0f, 20.0f);
		if (FMath::Abs(ZoneOffset) > FMath::Abs(StrongestOffset))
		{
			StrongestOffset = ZoneOffset;
		}
	}

	return StrongestOffset;
}

void UAgrarianSurvivalComponent::OnRep_Survival()
{
	BroadcastSurvivalChanged();
}

void UAgrarianSurvivalComponent::OnRep_CareHistory()
{
	ClampCareHistory();
	BroadcastSurvivalChanged();
}

void UAgrarianSurvivalComponent::ClampSurvival()
{
	Survival.Health = FMath::Clamp(Survival.Health, 0.0f, 100.0f);
	Survival.Stamina = FMath::Clamp(Survival.Stamina, 0.0f, 100.0f);
	Survival.Exhaustion = FMath::Clamp(Survival.Exhaustion, 0.0f, 100.0f);
	Survival.Hunger = FMath::Clamp(Survival.Hunger, 0.0f, 100.0f);
	Survival.Thirst = FMath::Clamp(Survival.Thirst, 0.0f, 100.0f);
	Survival.BodyTemperature = FMath::Clamp(Survival.BodyTemperature, 30.0f, 42.0f);
	Survival.InjurySeverity = FMath::Clamp(Survival.InjurySeverity, 0.0f, 100.0f);
	Survival.BleedingSeverity = FMath::Clamp(Survival.BleedingSeverity, 0.0f, 100.0f);
	Survival.SprainSeverity = FMath::Clamp(Survival.SprainSeverity, 0.0f, 100.0f);
	Survival.SicknessSeverity = FMath::Clamp(Survival.SicknessSeverity, 0.0f, 100.0f);
	UpdateDeathState();
}

void UAgrarianSurvivalComponent::ClampCareHistory()
{
	CareHistory.NutritionQuality = FMath::Clamp(CareHistory.NutritionQuality, 0.0f, 1.0f);
	CareHistory.IllnessBurden = FMath::Clamp(CareHistory.IllnessBurden, 0.0f, 1.0f);
	CareHistory.InjuryBurden = FMath::Clamp(CareHistory.InjuryBurden, 0.0f, 1.0f);
	CareHistory.SleepQuality = FMath::Clamp(CareHistory.SleepQuality, 0.0f, 1.0f);
	CareHistory.ShelterQuality = FMath::Clamp(CareHistory.ShelterQuality, 0.0f, 1.0f);
	CareHistory.StressBurden = FMath::Clamp(CareHistory.StressBurden, 0.0f, 1.0f);
	CareHistory.WorkloadBurden = FMath::Clamp(CareHistory.WorkloadBurden, 0.0f, 1.0f);
	CareHistory.TreatmentQuality = FMath::Clamp(CareHistory.TreatmentQuality, 0.0f, 1.0f);
}

void UAgrarianSurvivalComponent::BroadcastSurvivalChanged()
{
	OnSurvivalChanged.Broadcast(Survival);

	if (bLastBroadcastDeathState != Survival.bIsDead || LastBroadcastDeathReason != Survival.LastDeathReason)
	{
		bLastBroadcastDeathState = Survival.bIsDead;
		LastBroadcastDeathReason = Survival.LastDeathReason;
		OnDeathStateChanged.Broadcast(Survival.bIsDead, Survival.LastDeathReason);
	}
}

void UAgrarianSurvivalComponent::UpdateDeathState()
{
	if (Survival.Health <= 0.0f)
	{
		Survival.Health = 0.0f;
		Survival.Stamina = 0.0f;
		Survival.bIsDead = true;
		if (Survival.LastDeathReason.IsNone())
		{
			Survival.LastDeathReason = FName(TEXT("health_depleted"));
		}
	}
	else if (!Survival.bIsDead)
	{
		Survival.LastDeathReason = NAME_None;
	}
}
