using Sandbox;
using Sandbox.Network;
using System;

namespace GravCoin;

public sealed class UpgradeSystem : Component
{
	[Sync] public int GasLevel { get; private set; }
	[Sync] public int CoolingLevel { get; private set; }
	[Sync] public int UpgradeRevision { get; private set; }
	[Sync] public bool InsiderUnlocked { get; private set; }
	[Sync] public bool GpuUnlocked { get; private set; }
	[Sync] public bool SatelliteUnlocked { get; private set; }
	[Sync] public float InsiderLastUsedAt { get; private set; } = -999_999f;
	[Sync] public float LastInsiderTip { get; private set; }
	[Sync] public int HashLevel { get; private set; }
	[Sync] public int RewardLevel { get; private set; }

	public float GasReduction => GasLevel switch { 1 => 0.05f, 2 => 0.10f, 3 => 0.20f, 4 => 0.35f, 5 => 0.55f, _ => 0f };
	public int ExtraBotSlots => CoolingLevel switch { 1 => 1, 2 => 2, 3 => 4, 4 => 5, 5 => 6, _ => 0 };
	public int MaxBots => 1 + ExtraBotSlots;
	public bool InsiderReady => InsiderUnlocked && (Time.Now - InsiderLastUsedAt) >= 7200f;

	public int MiningClicks => HashLevel switch { 1 => 11, 2 => 8, 3 => 6, _ => 14 };
	public int MiningReward => RewardLevel switch { 1 => 240, 2 => 1200, 3 => 6500, _ => 120 };

	private const double VpnBaseCost = 3_500.0;
	private const double VpnGrowth = 2.40;
	private const double ServerBaseCost = 12_000.0;
	private const double ServerGrowth = 14.0;
	private const double HashBaseCost = 2_800.0;
	private const double HashGrowth = 3.50;
	private const double RewardBaseCost = 4_000.0;
	private const double RewardGrowth = 3.80;

	public const double GpuUnlockCost = 3_000_000.0;
	public const double SatelliteUnlockCost = 650_000_000.0;
	public const double InsiderUnlockCost = 8_000_000.0;

	public double NextVpnCost => GasLevel >= 5 ? 0 : GetVpnCostForLevel( GasLevel + 1 );
	public double NextServerCost => CoolingLevel >= 5 ? 0 : GetServerCostForLevel( CoolingLevel + 1 );
	public double NextHashCost => HashLevel >= 3 ? 0 : GetHashCostForLevel( HashLevel + 1 );
	public double NextRewardCost => RewardLevel >= 3 ? 0 : GetRewardCostForLevel( RewardLevel + 1 );

	private static double ExpoCost( double baseCost, double growth, int level )
	{
		if ( level <= 0 ) return 0;
		return Math.Round( baseCost * Math.Pow( growth, level - 1 ), 0 );
	}

	public static double GetVpnCostForLevel( int level ) => (level < 1 || level > 5) ? 0 : ExpoCost( VpnBaseCost, VpnGrowth, level );
	public static double GetServerCostForLevel( int level ) => (level < 1 || level > 5) ? 0 : ExpoCost( ServerBaseCost, ServerGrowth, level );
	public static double GetHashCostForLevel( int level ) => (level < 1 || level > 3) ? 0 : ExpoCost( HashBaseCost, HashGrowth, level );
	public static double GetRewardCostForLevel( int level ) => (level < 1 || level > 3) ? 0 : ExpoCost( RewardBaseCost, RewardGrowth, level );

	public static string HashLabel( int level ) => level switch
	{
		0 => $"Lv1 - 11 clicks/block (${PlayerTrader.FmtM( GetHashCostForLevel(1) )})",
		1 => $"Lv2 - 8 clicks/block (${PlayerTrader.FmtM( GetHashCostForLevel(2) )})",
		2 => $"Lv3 - 6 clicks/block (${PlayerTrader.FmtM( GetHashCostForLevel(3) )})",
		_ => "MAX"
	};

	public static string RewardLabel( int level ) => level switch
	{
		0 => $"Lv1 - $240 min payout (${PlayerTrader.FmtM( GetRewardCostForLevel(1) )})",
		1 => $"Lv2 - $1.2K min (${PlayerTrader.FmtM( GetRewardCostForLevel(2) )})",
		2 => $"Lv3 - $6.5K min (${PlayerTrader.FmtM( GetRewardCostForLevel(3) )})",
		_ => "MAX"
	};

	public static string VpnLabel( int level ) => level switch
	{
		0 => $"Lv1 -5% network fee (${PlayerTrader.FmtM( GetVpnCostForLevel(1) )})",
		1 => $"Lv2 -10% fee (${PlayerTrader.FmtM( GetVpnCostForLevel(2) )})",
		2 => $"Lv3 -20% fee (${PlayerTrader.FmtM( GetVpnCostForLevel(3) )})",
		3 => $"Lv4 -35% fee (${PlayerTrader.FmtM( GetVpnCostForLevel(4) )})",
		4 => $"Lv5 -55% fee (${PlayerTrader.FmtM( GetVpnCostForLevel(5) )})",
		_ => "MAX"
	};

	public static string ServerLabel( int level ) => level switch
	{
		0 => $"Lv1 Max 2 bots (${PlayerTrader.FmtM( GetServerCostForLevel(1) )})",
		1 => $"Lv2 Max 3 bots (${PlayerTrader.FmtM( GetServerCostForLevel(2) )})",
		2 => $"Lv3 Max 5 bots (${PlayerTrader.FmtM( GetServerCostForLevel(3) )})",
		3 => $"Lv4 Max 6 bots (${PlayerTrader.FmtM( GetServerCostForLevel(4) )})",
		4 => $"Lv5 Max 7 bots (${PlayerTrader.FmtM( GetServerCostForLevel(5) )})",
		_ => "MAX"
	};

	private bool _isDataLoaded;
	private bool _loadStarted;
	private PlayerTrader Owner => Components.Get<PlayerTrader>( FindMode.EverythingInSelfAndAncestors );
	private bool IsLocalOwner => Network.Owner is null || Network.Owner == Connection.Local;

	protected override void OnStart()
	{
		if ( IsProxy || !IsLocalOwner ) return;
		InitUpgrades();
	}

	protected override void OnFixedUpdate()
	{
		if ( !IsProxy && IsLocalOwner && !_loadStarted )
			InitUpgrades();
	}

	private void InitUpgrades()
	{
		if ( _loadStarted ) return;
		_loadStarted = true;
		_isDataLoaded = true;
	}

	protected override void OnDestroy()
	{
		if ( IsProxy || !_isDataLoaded || !IsLocalOwner ) return;
		Owner?.RequestSave();
	}

	public bool TryUpgradeVpn()
	{
		if ( GasLevel >= 5 ) return false;
		return TryApplyUpgradePurchase( NextVpnCost, () => GasLevel++ );
	}

	public bool TryUpgradeServer()
	{
		if ( CoolingLevel >= 5 ) return false;
		return TryApplyUpgradePurchase( NextServerCost, () => CoolingLevel++ );
	}

	public bool TryUnlockGpu()
	{
		if ( GpuUnlocked ) return false;
		return TryApplyUpgradePurchase( GpuUnlockCost, () => GpuUnlocked = true );
	}

	public bool TryUnlockSatellite()
	{
		if ( SatelliteUnlocked ) return false;
		return TryApplyUpgradePurchase( SatelliteUnlockCost, () => SatelliteUnlocked = true );
	}

	public bool TryUnlockInsider()
	{
		if ( InsiderUnlocked ) return false;
		return TryApplyUpgradePurchase( InsiderUnlockCost, () => InsiderUnlocked = true );
	}

	public float? UseInsiderTip()
	{
		if ( Owner?.CanProcessGameplay() != true ) return null;
		if ( !InsiderReady ) return null;
		InsiderLastUsedAt = Time.Now;
		LastInsiderTip = MathF.Sin( (Time.Now * 0.05f + 30f) * 1.3f );
		CommitUpgradeState();
		return LastInsiderTip;
	}

	public bool TryUpgradeHash()
	{
		if ( HashLevel >= 3 ) return false;
		return TryApplyUpgradePurchase( NextHashCost, () => HashLevel++ );
	}

	public bool TryUpgradeReward()
	{
		if ( RewardLevel >= 3 ) return false;
		return TryApplyUpgradePurchase( NextRewardCost, () => RewardLevel++ );
	}

	public void ResetUpgrades()
	{
		if ( IsProxy ) return;
		GasLevel = 0;
		CoolingLevel = 0;
		InsiderUnlocked = false;
		GpuUnlocked = false;
		SatelliteUnlocked = false;
		HashLevel = 0;
		RewardLevel = 0;
		InsiderLastUsedAt = -999_999f;
		LastInsiderTip = 0;
		CommitUpgradeState();
	}

	public void GrantHeritageInsider()
	{
		if ( IsProxy ) return;
		InsiderUnlocked = true;
		CommitUpgradeState();
	}

	public void GrantHeritageMaxVpn()
	{
		if ( IsProxy ) return;
		GasLevel = 5;
		CommitUpgradeState();
	}

	public void GrantHeritageBotFarm()
	{
		if ( IsProxy ) return;
		if ( CoolingLevel < 2 ) CoolingLevel = 2;
		CommitUpgradeState();
	}

	public UpgradeSaveData ExportSaveData()
	{
		return new UpgradeSaveData
		{
				GasLevel = GasLevel,
				CoolingLevel = CoolingLevel,
				UpgradeRevision = UpgradeRevision,
				InsiderUnlocked = InsiderUnlocked,
				GpuUnlocked = GpuUnlocked,
				SatelliteUnlocked = SatelliteUnlocked,
			InsiderLastUsedAt = InsiderLastUsedAt,
			LastInsiderTip = LastInsiderTip,
			HashLevel = HashLevel,
			RewardLevel = RewardLevel
		};
	}

	public void ImportSaveData( UpgradeSaveData data )
	{
		data ??= new UpgradeSaveData();
		GasLevel = Math.Clamp( data.GasLevel, 0, 5 );
		CoolingLevel = Math.Clamp( data.CoolingLevel, 0, 5 );
		UpgradeRevision = Math.Max( 0, data.UpgradeRevision );
		InsiderUnlocked = data.InsiderUnlocked;
		GpuUnlocked = data.GpuUnlocked;
		SatelliteUnlocked = data.SatelliteUnlocked;
		InsiderLastUsedAt = data.InsiderLastUsedAt;
		LastInsiderTip = data.LastInsiderTip;
		HashLevel = Math.Clamp( data.HashLevel, 0, 3 );
		RewardLevel = Math.Clamp( data.RewardLevel, 0, 3 );
		_isDataLoaded = true;
	}

	private bool TryApplyUpgradePurchase( double cost, Action applyEffect )
	{
		if ( IsProxy || !_isDataLoaded || !IsLocalOwner ) return false;
		if ( Owner?.CanProcessGameplay() != true ) return false;
		if ( cost <= 0 || double.IsNaN( cost ) || double.IsInfinity( cost ) ) return false;
		if ( !Owner.DeductMoney( cost ) ) return false;

		try
		{
			applyEffect?.Invoke();
			CommitUpgradeState();
			if ( WorldEventManager.Instance?.UpgradeSound is not null )
				Sound.Play( WorldEventManager.Instance.UpgradeSound );
			return true;
		}
		catch
		{
			Owner.AddMoney( cost );
			return false;
		}
	}

	private void CommitUpgradeState()
	{
		UpgradeRevision++;
		Network.Refresh();
		// Eagerly push the current snapshot into PlayerTrader._lastKnownUpgradeData so that
		// even if ForceFlushOnExitAsync runs after UpgradeSystem is already destroyed (and
		// ExportSaveData() returns null), the fallback path still has the correct values.
		Owner?.NotifyUpgradeCommitted( ExportSaveData() );
		RequestOwnerSave();
	}

	private void RequestOwnerSave()
	{
		if ( !_isDataLoaded || !IsLocalOwner ) return;
		if ( Owner?.CanProcessGameplay() != true ) return;
		// Use a critical (non-rate-limited) save so upgrades are always persisted immediately,
		// regardless of whether a regular save happened in the last 10 seconds.
		Owner.RequestCriticalSave( "upgrade" );
	}
}
