using Sandbox;
using Sandbox.Network;
using System;
using System.Collections.Generic;

namespace GravCoin;

/// <summary>
/// Idle-style infrastructure economy manager (manual cycles + automation).
/// </summary>
public sealed class InfrastructureManager : Component
{
	[Sync] public int Revision { get; private set; }

	private readonly List<InfrastructureItem> _items = new();
	private bool _initialized;

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

	public IReadOnlyList<InfrastructureItem> Items => _items;
	public event Action<int, double> OnCyclePayout;

	protected override void OnStart()
	{
		EnsureInitialized();
	}

	protected override void OnFixedUpdate()
	{
		if ( IsProxy || !IsLocalOwner ) return;
		EnsureInitialized();
		TickCycles();
	}

	private void EnsureInitialized()
	{
		if ( _initialized ) return;
		_initialized = true;
		_items.Clear();
		// Idle balance target:
		// - Early: manual Earn Hub stays primary
		// - Mid: bots + T2/T3 infra become comparable
		// - Late: T4/T5 infra turns into the main faucet, with long-cycle high payouts
		_items.Add( new InfrastructureItem( "pi_cluster", "Raspberry Pi Cluster", "mdi:raspberry-pi", "Starter micro-nodes for passive trickle.", 150, 1.6, 2f ) );
		_items.Add( new InfrastructureItem( "gpu_rig", "GPU Mining Rig", "mdi:gpu", "Entry automation for sustained throughput.", 7_500, 360, 12f ) );
		_items.Add( new InfrastructureItem( "edge_rack", "Edge Server Rack", "mdi:server-network", "Regional compute stack for mid-game growth.", 350_000, 54_000, 45f ) );
		_items.Add( new InfrastructureItem( "mod_dc", "Modular Datacenter", "mdi:database", "Industrial backbone with long, heavy cycles.", 28_000_000, 21_000_000, 300f ) );
		_items.Add( new InfrastructureItem( "orbital_node", "Orbital Cloud Node", "mdi:satellite-variant", "Late-game macro faucet with massive payouts.", 1_400_000_000, 2_100_000_000, 600f ) );
		_items.Add( new InfrastructureItem( "planetary_mesh", "Planetary Compute Grid", "mdi:earth", "Intercontinental mesh for hyper-scale throughput.", 180_000_000_000_000, 1_440_000_000_000_000, 720f ) );
		_items.Add( new InfrastructureItem( "dyson_cluster", "Dyson Quantum Cluster", "mdi:star-four-points", "Stellar-grade compute with epoch-level payouts.", 2_400_000_000_000_000, 28_800_000_000_000_000, 900f ) );
	}

	private bool CanProcess()
	{
		return Owner?.CanProcessGameplay() == true && !IsProxy && IsLocalOwner;
	}

	private bool TryGetItem( int machineIndex, out InfrastructureItem item )
	{
		item = null;
		if ( machineIndex < 0 || machineIndex >= _items.Count ) return false;
		item = _items[machineIndex];
		return item is not null;
	}

	private void TouchRevision( bool requestSave = true )
	{
		Revision++;
		Network.Refresh();
		if ( requestSave )
			Owner?.RequestCriticalSave( "infrastructure_revision" );
	}

	private double ApplyWelcomeBoostToPayout( double amount )
	{
		if ( amount <= 0 ) return 0;
		return Owner?.ApplyWelcomeBoostToGain( amount ) ?? amount;
	}

	public double GetDisplayedRevenuePerCycle( InfrastructureItem item )
	{
		if ( item is null ) return 0;
		return ApplyWelcomeBoostToPayout( item.RevenuePerCycle );
	}

	[Rpc.Owner]
	public void CmdBuyMachine( int machineIndex, int quantity )
	{
		if ( !CanProcess() ) return;
		if ( !TryGetItem( machineIndex, out var item ) ) return;

		quantity = Math.Clamp( quantity, 1, 100 );
		double cost = item.CostForQuantity( quantity );
		if ( !Owner.DeductMoney( cost ) ) return;

		item.Owned += quantity;
		if ( item.AutomationUnlocked && !item.IsCycleRunning )
		{
			item.IsCycleRunning = true;
			item.CycleStartedAt = Time.Now;
		}
		TouchRevision();
	}

	public bool EnsureStarterMachineOwned( int machineIndex = 0, bool requestSave = true )
	{
		if ( IsProxy || !IsLocalOwner ) return false;
		EnsureInitialized();
		if ( !TryGetItem( machineIndex, out var item ) ) return false;

		bool changed = false;
		if ( item.Owned < 1 )
		{
			item.Owned = 1;
			changed = true;
		}

		if ( item.Owned > 0 && !item.IsCycleRunning )
		{
			item.IsCycleRunning = true;
			item.CycleStartedAt = Time.Now;
			changed = true;
		}

		if ( changed )
			TouchRevision( requestSave );

		return changed;
	}

	[Rpc.Owner]
	public void CmdStartCycle( int machineIndex )
	{
		if ( !CanProcess() ) return;
		if ( !TryGetItem( machineIndex, out var item ) ) return;
		if ( item.Owned <= 0 ) return;
		if ( item.AutomationUnlocked ) return;
		if ( item.IsCycleRunning ) return;

		item.IsCycleRunning = true;
		item.CycleStartedAt = Time.Now;
		TouchRevision();
	}

	[Rpc.Owner]
	public void CmdBuyAutomation( int machineIndex )
	{
		if ( !CanProcess() ) return;
		if ( !TryGetItem( machineIndex, out var item ) ) return;
		if ( item.Owned <= 0 || item.AutomationUnlocked ) return;
		if ( !Owner.DeductMoney( item.AutomationCost ) ) return;

		item.AutomationUnlocked = true;
		item.IsCycleRunning = true;
		item.CycleStartedAt = Time.Now;
		TouchRevision();
	}

	[Rpc.Owner]
	public void CmdBuySpeedUpgrade( int machineIndex )
	{
		if ( !CanProcess() ) return;
		if ( !TryGetItem( machineIndex, out var item ) ) return;
		if ( item.Owned <= 0 || item.SpeedUpgradeUnlocked ) return;
		if ( !Owner.DeductMoney( item.SpeedUpgradeCost ) ) return;

		double priorProgress = item.ProgressPercent( Time.Now ) / 100.0;
		item.SpeedUpgradeUnlocked = true;
		if ( item.IsCycleRunning )
		{
			double newDuration = Math.Max( 0.05, item.EffectiveCycleSeconds );
			item.CycleStartedAt = Time.Now - (newDuration * priorProgress);
		}
		TouchRevision();
	}

	[Rpc.Owner]
	public void CmdBuyProfitUpgrade( int machineIndex )
	{
		if ( !CanProcess() ) return;
		if ( !TryGetItem( machineIndex, out var item ) ) return;
		if ( item.Owned <= 0 || !item.SpeedUpgradeUnlocked || item.ProfitUpgradeUnlocked ) return;
		if ( !Owner.DeductMoney( item.ProfitUpgradeCost ) ) return;

		item.ProfitUpgradeUnlocked = true;
		TouchRevision();
	}

	private void TickCycles()
	{
		if ( !CanProcess() ) return;

		double now = Time.Now;
		for ( int i = 0; i < _items.Count; i++ )
		{
			var item = _items[i];
			if ( item.Owned <= 0 )
			{
				item.IsCycleRunning = false;
				continue;
			}

			if ( item.AutomationUnlocked && !item.IsCycleRunning )
			{
				item.IsCycleRunning = true;
				item.CycleStartedAt = now;
			}

			if ( !item.IsCycleRunning ) continue;

			double duration = Math.Max( 0.05, item.EffectiveCycleSeconds );
			double elapsed = now - item.CycleStartedAt;
			if ( elapsed < duration ) continue;

			int cycles = item.AutomationUnlocked ? Math.Max( 1, (int)Math.Floor( elapsed / duration ) ) : 1;
			double payout = ApplyWelcomeBoostToPayout( item.RevenuePerCycle * cycles );

			if ( payout > 0 )
			{
				Owner.AddMoney( payout );
				Owner.TrackPassiveIncome( payout, $"{item.Name} payout" );
				item.LifetimeEarned += payout;
				OnCyclePayout?.Invoke( i, payout );
			}

			if ( item.AutomationUnlocked )
			{
				double remainder = elapsed - cycles * duration;
				item.CycleStartedAt = now - Math.Max( 0.0, remainder );
			}
			else
			{
				item.IsCycleRunning = false;
			}
		}
	}

	public double SimulateOfflineEarnings( int offlineSeconds )
	{
		EnsureInitialized();
		offlineSeconds = Math.Clamp( offlineSeconds, 0, 86400 );
		if ( offlineSeconds <= 0 ) return 0;

		double now = Time.Now;
		double total = 0;
		for ( int i = 0; i < _items.Count; i++ )
		{
			var item = _items[i];
			if ( item.Owned <= 0 )
			{
				item.IsCycleRunning = false;
				continue;
			}

			double duration = Math.Max( 0.05, item.EffectiveCycleSeconds );
			if ( item.AutomationUnlocked )
			{
				int cycles = Math.Max( 0, (int)Math.Floor( offlineSeconds / duration ) );
				if ( cycles > 0 )
				{
					double gained = ApplyWelcomeBoostToPayout( (item.RevenuePerCycle * cycles) * 0.50 ); // 50% offline penalty
					total += gained;
					item.LifetimeEarned += gained;
				}

				double remainder = offlineSeconds - cycles * duration;
				item.IsCycleRunning = true;
				item.CycleStartedAt = now - Math.Max( 0.0, remainder );
				continue;
			}

			if ( !item.IsCycleRunning ) continue;

			double elapsed = Math.Max( 0.0, now - item.CycleStartedAt );
			if ( elapsed + offlineSeconds >= duration )
			{
				double gained = ApplyWelcomeBoostToPayout( item.RevenuePerCycle * 0.50 ); // 50% offline penalty
				total += gained;
				item.LifetimeEarned += gained;
				item.IsCycleRunning = false;
			}
		}

		return Math.Round( total, 2 );
	}

	public double CurrentAutomatedIncomePerSecond()
	{
		EnsureInitialized();
		double total = 0;
		for ( int i = 0; i < _items.Count; i++ )
		{
			var item = _items[i];
			if ( item.Owned <= 0 || !item.AutomationUnlocked ) continue;
			double duration = Math.Max( 0.05, item.EffectiveCycleSeconds );
			total += GetDisplayedRevenuePerCycle( item ) / duration;
		}
		return total;
	}

	public double ComputeAssetValue()
	{
		EnsureInitialized();
		double total = 0;
		for ( int i = 0; i < _items.Count; i++ )
			total += _items[i].ApproxAssetValue();
		return total;
	}

	public void ResetInfrastructure()
	{
		EnsureInitialized();
		for ( int i = 0; i < _items.Count; i++ )
		{
			var item = _items[i];
			item.Owned = 0;
			item.AutomationUnlocked = false;
			item.SpeedUpgradeUnlocked = false;
			item.ProfitUpgradeUnlocked = false;
			item.IsCycleRunning = false;
			item.CycleStartedAt = 0;
			item.LifetimeEarned = 0;
		}
		TouchRevision();
	}

	public InfrastructureSaveData ExportSaveData()
	{
		EnsureInitialized();
		double now = Time.Now;

		var data = new InfrastructureSaveData
		{
			Revision = Revision
		};

		for ( int i = 0; i < _items.Count; i++ )
		{
			var item = _items[i];
			double elapsed = item.IsCycleRunning ? Math.Max( 0.0, now - item.CycleStartedAt ) : 0.0;
			float clampedElapsed = (float)Math.Clamp( elapsed, 0.0, Math.Max( 0.05, item.EffectiveCycleSeconds ) );
			data.Machines.Add( new InfrastructureMachineSaveData
			{
				Id = item.Id,
				Owned = item.Owned,
				AutomationUnlocked = item.AutomationUnlocked,
				SpeedUpgradeUnlocked = item.SpeedUpgradeUnlocked,
				ProfitUpgradeUnlocked = item.ProfitUpgradeUnlocked,
				IsCycleRunning = item.IsCycleRunning,
				CycleElapsedSeconds = clampedElapsed,
				LifetimeEarned = item.LifetimeEarned
			} );
		}

		return data;
	}

	public void ImportSaveData( InfrastructureSaveData data )
	{
		EnsureInitialized();
		data ??= new InfrastructureSaveData();
		double now = Time.Now;

		var map = new Dictionary<string, InfrastructureMachineSaveData>( StringComparer.OrdinalIgnoreCase );
		if ( data.Machines is not null )
		{
			for ( int i = 0; i < data.Machines.Count; i++ )
			{
				var entry = data.Machines[i];
				if ( entry is null || string.IsNullOrWhiteSpace( entry.Id ) ) continue;
				map[entry.Id] = entry;
			}
		}

		for ( int i = 0; i < _items.Count; i++ )
		{
			var item = _items[i];
			if ( map.TryGetValue( item.Id, out var saved ) )
			{
				item.Owned = Math.Max( 0, saved.Owned );
				item.AutomationUnlocked = saved.AutomationUnlocked;
				item.SpeedUpgradeUnlocked = saved.SpeedUpgradeUnlocked;
				item.ProfitUpgradeUnlocked = saved.ProfitUpgradeUnlocked;
				item.IsCycleRunning = saved.IsCycleRunning && item.Owned > 0;
				item.LifetimeEarned = Math.Max( 0.0, saved.LifetimeEarned );

				float elapsed = Math.Clamp( saved.CycleElapsedSeconds, 0f, Math.Max( 0.05f, item.EffectiveCycleSeconds ) );
				item.CycleStartedAt = now - elapsed;
				if ( item.AutomationUnlocked && item.Owned > 0 && !item.IsCycleRunning )
				{
					item.IsCycleRunning = true;
					item.CycleStartedAt = now;
				}
			}
			else
			{
				item.Owned = 0;
				item.AutomationUnlocked = false;
				item.SpeedUpgradeUnlocked = false;
				item.ProfitUpgradeUnlocked = false;
				item.IsCycleRunning = false;
				item.CycleStartedAt = 0;
				item.LifetimeEarned = 0;
			}
		}

		Revision = Math.Max( Revision, data.Revision );
		Network.Refresh();
	}
}
