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

namespace GravCoin;

public enum MissionCategory
{
	Trading,
	Infrastructure,
	Earn
}

/// <summary>
/// Runtime mission tracking and claiming.
/// Mission progress is evaluated from live gameplay stats,
/// while claim state is persisted in local JSON save.
/// </summary>
public sealed class MissionManager : Component
{
	public readonly record struct MissionDefinition(
		string Id,
		MissionCategory Category,
		string Title,
		string Description,
		double Target,
		double RewardCash );

	public readonly record struct MissionSnapshot(
		string Id,
		MissionCategory Category,
		string Title,
		string Description,
		double Progress,
		double Target,
		double RewardCash,
		bool IsComplete,
		bool IsClaimed );

	[Sync] public int Revision { get; private set; }

	private static readonly MissionDefinition[] Definitions =
	{
		// Trading missions (early -> late)
		// Early rewards are intentionally conservative because trade-count objectives
		// can be rushed by rapidly slicing manual sells into many tiny actions.
		new( "trade_10", MissionCategory.Trading, "First 10 Trades", "Complete 10 manual trades.", 10, 500 ),
		new( "trade_50", MissionCategory.Trading, "Tape Reader", "Complete 50 manual trades.", 50, 2_000 ),
		new( "trade_200", MissionCategory.Trading, "Session Grinder", "Complete 200 manual trades.", 200, 12_000 ),
		new( "trade_5000", MissionCategory.Trading, "Orderflow Machine", "Complete 5,000 manual trades.", 5_000, 120_000 ),

		new( "trade_profit_100k", MissionCategory.Trading, "Desk Runner", "Reach $100K total profit.", 100_000, 5_000 ),
		new( "trade_profit_2m", MissionCategory.Trading, "Cash Velocity", "Reach $2M total profit.", 2_000_000, 90_000 ),
		new( "trade_profit_75m", MissionCategory.Trading, "Street Shark", "Reach $75M total profit.", 75_000_000, 8_500_000 ),
		new( "trade_profit_5b", MissionCategory.Trading, "Whale Desk", "Reach $5B total profit.", 5_000_000_000, 520_000_000 ),
		new( "trade_profit_500b", MissionCategory.Trading, "Market Overlord", "Reach $500B total profit.", 500_000_000_000, 44_000_000_000 ),

		new( "bots_own_1", MissionCategory.Trading, "First Bot Online", "Own 1 trading bot.", 1, 1_500 ),
		new( "bots_own_3", MissionCategory.Trading, "Bot Operator", "Own 3 trading bots.", 3, 12_000 ),
		new( "bots_own_5", MissionCategory.Trading, "Bot Swarm", "Own 5 trading bots.", 5, 180_000 ),

		new( "hotstreak_3", MissionCategory.Trading, "Momentum Pulse", "Reach a best hot streak of 3.", 3, 1_500 ),
		new( "hotstreak_8", MissionCategory.Trading, "Unbreakable Tape", "Reach a best hot streak of 8.", 8, 90_000 ),

		// Fun objectives
		new( "panic_sell_1", MissionCategory.Trading, "Red Button", "Trigger Panic Sell once.", 1, 5_000 ),
		new( "panic_sell_5", MissionCategory.Trading, "Circuit Breaker", "Trigger Panic Sell 5 times.", 5, 900_000 ),

		// Infrastructure missions (early -> late)
		new( "infra_automation_1", MissionCategory.Infrastructure, "Automation Online", "Unlock automation on 1 infrastructure.", 1, 2_000 ),
		new( "infra_automation_3", MissionCategory.Infrastructure, "Factory Loop", "Unlock automation on 3 infrastructures.", 3, 30_000 ),
		new( "infra_automation_6", MissionCategory.Infrastructure, "Lights-Out Ops", "Unlock automation on 6 infrastructures.", 6, 9_000_000 ),

		new( "infra_owned_20", MissionCategory.Infrastructure, "Small Datacenter", "Own 20 infrastructure units.", 20, 10_000 ),
		new( "infra_owned_75", MissionCategory.Infrastructure, "Factory Network", "Own 75 infrastructure units.", 75, 350_000 ),
		new( "infra_owned_200", MissionCategory.Infrastructure, "Compute District", "Own 200 infrastructure units.", 200, 8_500_000 ),
		new( "infra_owned_600", MissionCategory.Infrastructure, "Megagrid Estate", "Own 600 infrastructure units.", 600, 120_000_000 ),

		new( "infra_speed_2", MissionCategory.Infrastructure, "Latency Hunter", "Unlock 2 speed upgrades.", 2, 176_000 ),
		new( "infra_profit_2", MissionCategory.Infrastructure, "Margin Engineer", "Unlock 2 profit upgrades.", 2, 224_000 ),
		new( "infra_upgrades_8", MissionCategory.Infrastructure, "Control Plane", "Unlock 8 total infrastructure upgrades.", 8, 34_000_000 ),

		new( "infra_lifetime_1m", MissionCategory.Infrastructure, "Warm Cache", "Generate $1M lifetime from infrastructure.", 1_000_000, 30_000 ),
		new( "infra_lifetime_250m", MissionCategory.Infrastructure, "Data Flood", "Generate $250M lifetime from infrastructure.", 250_000_000, 32_000_000 ),
		new( "infra_lifetime_25b", MissionCategory.Infrastructure, "Industrial Throughput", "Generate $25B lifetime from infrastructure.", 25_000_000_000, 4_200_000_000 ),

		new( "infra_cps_2k", MissionCategory.Infrastructure, "Passive Pulse", "Reach $2,000/sec automated infrastructure income.", 2_000, 85_000 ),
		new( "infra_cps_250k", MissionCategory.Infrastructure, "Server River", "Reach $250,000/sec automated infrastructure income.", 250_000, 65_000_000 ),
		new( "infra_cps_25m", MissionCategory.Infrastructure, "Planetary Throughput", "Reach $25M/sec automated infrastructure income.", 25_000_000, 8_500_000_000 ),

		// Earn / progression missions (early -> late)
		new( "earn_mine_25", MissionCategory.Earn, "Hands On", "Mine 25 blocks in Earn Hub.", 25, 1_200 ),
		new( "earn_mine_150", MissionCategory.Earn, "Signal Specialist", "Mine 150 blocks in Earn Hub.", 200, 18_000 ),
		new( "earn_mine_600", MissionCategory.Earn, "Hash Cadence", "Mine 600 blocks in Earn Hub.", 1_000, 220_000 ),
		new( "earn_mine_2500", MissionCategory.Earn, "Nonce Engine", "Mine 2,500 blocks in Earn Hub.", 5_000, 28_000_000 ),

		new( "first_big_win", MissionCategory.Earn, "First Big Win", "Trigger your first bonus achievement payout.", 1, 1_000 ),
		new( "daily_streak_3", MissionCategory.Earn, "Routine Builder", "Reach a daily login streak of 3.", 3, 50_000 ),
		new( "daily_streak_7", MissionCategory.Earn, "Weekly Discipline", "Reach a daily login streak of 7.", 7, 1_000_000 ),

		new( "networth_5m", MissionCategory.Earn, "Momentum Breakout", "Reach $5M net worth.", 5_000_000, 90_000 ),
		new( "networth_1b", MissionCategory.Earn, "Billion Blueprint", "Reach $1B net worth.", 1_000_000_000, 120_000_000 ),
		new( "networth_100t", MissionCategory.Earn, "Omnipotent Threshold", "Reach $100T net worth.", 100_000_000_000_000, 7_500_000_000_000 ),
		new( "networth_1qa", MissionCategory.Earn, "Post-Singularity", "Reach $1Qa net worth.", 1_000_000_000_000_000, 120_000_000_000_000 ),
	};

	private static readonly Dictionary<string, MissionDefinition> DefinitionMap =
		Definitions.ToDictionary( d => d.Id, StringComparer.OrdinalIgnoreCase );

	private readonly HashSet<string> _claimedMissionIds = new( StringComparer.OrdinalIgnoreCase );

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

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

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

	private int BotCount() => Math.Max( 0, Owner?.OwnedBots.Count( b => b.IsValid() ) ?? 0 );
	private int InfrastructureOwnedTotal() => Math.Max( 0, Infrastructure?.Items.Sum( m => Math.Max( 0, m.Owned ) ) ?? 0 );
	private int InfrastructureAutomationCount() => Math.Max( 0, Infrastructure?.Items.Count( m => m.Owned > 0 && m.AutomationUnlocked ) ?? 0 );
	private int InfrastructureSpeedUpgradeCount() => Math.Max( 0, Infrastructure?.Items.Count( m => m.Owned > 0 && m.SpeedUpgradeUnlocked ) ?? 0 );
	private int InfrastructureProfitUpgradeCount() => Math.Max( 0, Infrastructure?.Items.Count( m => m.Owned > 0 && m.ProfitUpgradeUnlocked ) ?? 0 );
	private int InfrastructureTotalUpgradeCount()
	{
		var items = Infrastructure?.Items;
		if ( items is null ) return 0;
		int total = 0;
		for ( int i = 0; i < items.Count; i++ )
		{
			var item = items[i];
			if ( item.Owned <= 0 ) continue;
			if ( item.AutomationUnlocked ) total++;
			if ( item.SpeedUpgradeUnlocked ) total++;
			if ( item.ProfitUpgradeUnlocked ) total++;
		}
		return Math.Max( 0, total );
	}

	private double InfrastructureLifetimeYield() => Math.Max( 0, Infrastructure?.Items.Sum( m => Math.Max( 0.0, m.LifetimeEarned ) ) ?? 0.0 );
	private double InfrastructureIncomePerSecond() => Math.Max( 0, Infrastructure?.CurrentAutomatedIncomePerSecond() ?? 0 );
	private double GetRewardCash( MissionDefinition definition )
	{
		double reward = Math.Round( Math.Max( 0.0, definition.RewardCash ), 2, MidpointRounding.AwayFromZero );
		return Owner?.ApplyWelcomeBoostToGain( reward ) ?? reward;
	}

	private double EvaluateProgress( string missionId )
	{
		var player = Owner;
		if ( player is null ) return 0;

		switch ( missionId )
		{
			case "trade_10":
			case "trade_50":
			case "trade_200":
			case "trade_1000":
			case "trade_5000":
				return Math.Max( 0, player.TotalTrades );

			case "trade_profit_100k":
			case "trade_profit_2m":
			case "trade_profit_75m":
			case "trade_profit_5b":
			case "trade_profit_500b":
				return Math.Max( 0, player.TotalProfit );

			case "bots_own_1":
				return Math.Max( BotCount(), Math.Max( 0, player.TotalBotsPurchased ) );
			case "bots_own_3":
			case "bots_own_5":
				return BotCount();

			case "hotstreak_3":
			case "hotstreak_8":
				return Math.Max( 0, player.BestStreak );

			case "panic_sell_1":
			case "panic_sell_5":
				return Math.Max( 0, player.PanicSellCount );

			case "infra_automation_1":
			case "infra_automation_3":
			case "infra_automation_6":
				return InfrastructureAutomationCount();

			case "infra_owned_20":
			case "infra_owned_75":
			case "infra_owned_200":
			case "infra_owned_600":
				return InfrastructureOwnedTotal();

			case "infra_speed_2":
				return InfrastructureSpeedUpgradeCount();
			case "infra_profit_2":
				return InfrastructureProfitUpgradeCount();
			case "infra_upgrades_8":
				return InfrastructureTotalUpgradeCount();

			case "infra_lifetime_1m":
			case "infra_lifetime_250m":
			case "infra_lifetime_25b":
				return InfrastructureLifetimeYield();

			case "infra_cps_2k":
			case "infra_cps_250k":
			case "infra_cps_25m":
				return InfrastructureIncomePerSecond();

			case "earn_mine_25":
			case "earn_mine_150":
			case "earn_mine_600":
			case "earn_mine_2500":
				return Math.Max( 0, player.TotalMinedCount() );

			case "first_big_win":
				return player.HasReceivedFirstWin ? 1 : 0;

			case "daily_streak_3":
			case "daily_streak_7":
				return Math.Max( 0, player.DailyStreak );

			case "networth_5m":
			case "networth_1b":
			case "networth_100t":
			case "networth_1qa":
				return Math.Max( 0, player.NetWorth );

			default:
				return 0;
		}
	}

	public IReadOnlyList<MissionSnapshot> GetMissions( MissionCategory? category = null )
	{
		var list = new List<MissionSnapshot>( Definitions.Length );
		for ( int i = 0; i < Definitions.Length; i++ )
		{
			var def = Definitions[i];
			if ( category.HasValue && def.Category != category.Value ) continue;

			double progressRaw = EvaluateProgress( def.Id );
			double progress = Math.Clamp( progressRaw, 0, def.Target );
			double rewardCash = GetRewardCash( def );
			bool isClaimed = _claimedMissionIds.Contains( def.Id );
			bool isComplete = progressRaw >= def.Target;

			list.Add( new MissionSnapshot(
				def.Id,
				def.Category,
				def.Title,
				def.Description,
				progress,
				def.Target,
				rewardCash,
				isComplete,
				isClaimed ) );
		}

		return list;
	}

	public int ClaimableCount( MissionCategory? category = null )
	{
		var missions = GetMissions( category );
		return missions.Count( m => m.IsComplete && !m.IsClaimed );
	}

	[Rpc.Owner]
	public void CmdClaimMission( string missionId )
	{
		if ( !CanProcess() ) return;
		if ( string.IsNullOrWhiteSpace( missionId ) ) return;
		missionId = missionId.Trim();

		if ( !DefinitionMap.TryGetValue( missionId, out var def ) ) return;
		if ( _claimedMissionIds.Contains( def.Id ) ) return;

		double progress = EvaluateProgress( def.Id );
		if ( progress < def.Target ) return;

		_claimedMissionIds.Add( def.Id );
		double reward = GetRewardCash( def );
		if ( reward > 0 )
		{
			Owner?.AddMoney( reward );
			Owner?.TrackPassiveIncome( reward, "MISSION CLAIM" );
		}

		TouchRevision();
	}

	public MissionSaveData ExportSaveData()
	{
		var data = new MissionSaveData
		{
			Revision = Revision,
			ClaimedMissionIds = _claimedMissionIds
				.OrderBy( x => x, StringComparer.OrdinalIgnoreCase )
				.ToList()
		};
		return data;
	}

	public void ImportSaveData( MissionSaveData data )
	{
		data ??= new MissionSaveData();
		_claimedMissionIds.Clear();

		if ( data.ClaimedMissionIds is not null )
		{
			for ( int i = 0; i < data.ClaimedMissionIds.Count; i++ )
			{
				string missionId = data.ClaimedMissionIds[i];
				if ( string.IsNullOrWhiteSpace( missionId ) ) continue;
				if ( DefinitionMap.ContainsKey( missionId ) )
					_claimedMissionIds.Add( missionId.Trim() );
			}
		}

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

	public void ResetMissions( bool requestSave = true )
	{
		_claimedMissionIds.Clear();
		TouchRevision( requestSave );
	}
}
