using Sandbox;
using Sandbox.UI;   // needed for EventAlertPanel
using System;
using System.Collections.Generic;
using System.Linq;

namespace GravCoin;

public enum EventRarity { Commun, Rare, Épique, Légendaire, UltraRare }

/// <summary>
/// Fires random world events with varying rarities and broadcasts them to all clients.
/// </summary>
public sealed class WorldEventManager : Component
{
	public static WorldEventManager Instance { get; private set; }

	[Property, Group( "Sounds" )] public SoundEvent EventTriggerSound { get; set; }
	[Property, Group( "Sounds" )] public SoundEvent BuySound { get; set; }
	[Property, Group( "Sounds" )] public SoundEvent SellSound { get; set; }
	[Property, Group( "Sounds" )] public SoundEvent MiningHashSound { get; set; }
	[Property, Group( "Sounds" )] public SoundEvent MiningRewardSound { get; set; }
	[Property, Group( "Sounds" )] public SoundEvent RankUpSound { get; set; }
	[Property, Group( "Sounds" )] public SoundEvent UpgradeSound { get; set; }

	private record struct WorldEvent(
		string Name, string Desc, string Color, EventRarity Rarity,
		float Multiplier, float Duration, int TaxBonus, float Drift, float Shock, int Weight);

	// ── Event Pool ────────────────────────────────────────────────────
	// Weight controls spawn frequency. Lower = rarer.
	// Drift is the per-tick price push (+/- but NOT guaranteed outcome).
	// Multiplier affects volatility (>1 = more volatile, <1 = calmer).
	// Drift is per-tick directional push. Shock is instant price jump on trigger.
	private readonly List<WorldEvent> _pool = new()
	{
		// ═══ COMMON (frequent, mild ~3-8% total) ═════════════════════
		new("Market Rumor",
			"Anonymous analysts predict an imminent reversal. Dubious source, but traders are panicking.",
			"warning", EventRarity.Commun, 1.25f, 120f, 0, -0.0012f, -0.002f, 25), // Dur: 100 -> 120

		new("Quarterly Report",
			"Graviton Corp's financial results exceed expectations. The market reacts positively.",
			"positive", EventRarity.Commun, 1.20f, 150f, 0, +0.0012f, +0.002f, 25), // Drift: +0.0008 -> +0.0012

		new("Viral Tweet",
			"A crypto influencer with 12M followers tweets 'GravCoin to the moon'. FOMO kicks in.",
			"positive", EventRarity.Commun, 1.35f, 120f, 0, +0.0018f, +0.003f, 20), // Dur: 100 -> 120

		new("Platform Audit",
			"The SEC launches a routine audit on trading platforms. Slight panic movement.",
			"warning", EventRarity.Commun, 1.15f, 130f, 3, -0.0009f, -0.001f, 20), // Drift: -0.0006 -> -0.0009

		new("Mining Difficulty",
			"GravCoin network difficulty jumps 12%. Small-scale miners are pulling out.",
			"warning", EventRarity.Commun, 1.18f, 150f, 0, +0.0012f, +0.003f, 20), // Drift: +0.0008 -> +0.0012

		new("Trending App",
			"GravCoin Wallet enters the Top 10 Finance apps. Influx of new retail investors.",
			"positive", EventRarity.Commun, 1.25f, 120f, 0, +0.0012f, +0.002f, 18), // Dur: 100 -> 120

		new("Reddit FUD",
			"A viral post on r/CryptoMoonShots accuses GravCoin of fraud. Quick damage control, but scars remain.",
			"negative", EventRarity.Commun, 1.25f, 120f, 0, -0.0018f, -0.003f, 18), // Dur: 100 -> 120

		// ═══ RARE (~38% total) ════════════════════════════════════════
		new("Strict Regulation",
			"The EU passes a 15% tax on all crypto transactions. Markets are under heavy pressure.",
			"negative", EventRarity.Rare, 1.55f, 155f, 12, -0.0020f, -0.010f, 19), // Dur: +29%

		new("Bots Flash Sale",
			"Trading server bug: all bots are sold at -50% for a short window!",
			"positive", EventRarity.Rare, 1.05f, 120f, 0, 0f, 0f, 19), // Dur: 100 -> 120

		new("Oil Crisis",
			"OPEC cuts production by 2M barrels/day. Crypto benefits as a digital safe haven.",
			"positive", EventRarity.Rare, 1.65f, 155f, 0, +0.0016f, +0.007f, 16), // Dur: +29%

		new("Global Outage",
			"A major Atlantic submarine cable is severed. 30% of global internet traffic slows down.",
			"negative", EventRarity.Rare, 0.90f, 155f, 5, -0.0020f, -0.008f, 16), // Dur: +29%

		new("Whale Alert",
			"Unknown wallet moves 847,000 GravCoin ($84M) to an exchange. Massive dump incoming?",
			"warning", EventRarity.Rare, 1.75f, 155f, 0, -0.0024f, -0.015f, 16), // Dur: +29%

		new("Institutional Adoption",
			"BlackRock announces adding GravCoin to its crypto ETF. Extremely bullish signal.",
			"positive", EventRarity.Rare, 1.65f, 155f, 0, +0.0040f, +0.030f, 13), // Dur: +29%

		new("Cascading Liquidations",
			"$230M of long positions liquidated in 15 minutes. Price spirals down in a brutal domino effect.",
			"negative", EventRarity.Rare, 2.00f, 155f, 0, -0.0060f, -0.035f, 13), // Dur: +29%

		new("Controversial Fork",
			"GravCoin community votes for a hard fork. Uncertainty reigns over which chain will survive.",
			"warning", EventRarity.Rare, 1.55f, 155f, 0, -0.0024f, -0.015f, 13), // Dur: +29%

		// ═══ ÉPIQUE (~11% total) ══════════════════════════════════════
		new("Geopolitical Conflict",
			"Military escalation Iran-Israel. Traditional markets crash, crypto attracts fleeing capital.",
			"warning", EventRarity.Épique, 1.55f, 155f, 0, +0.0024f, +0.018f, 8),

		new("Fed Panic Cut",
			"The Federal Reserve triggers an emergency 75bps rate cut. Cheap money floods into crypto.",
			"positive", EventRarity.Épique, 1.55f, 155f, 0, +0.0030f, +0.025f, 6),

		new("Major Hack",
			"Exchange hacked: $400M in crypto stolen. General panic. Withdrawals skyrocket.",
			"negative", EventRarity.Épique, 1.70f, 155f, 6, -0.0040f, -0.028f, 6),

		new("China Ban (Again)",
			"China bans crypto mining for the 7th time. Massive seizures. Global hashrate drops 35%.",
			"negative", EventRarity.Épique, 1.55f, 155f, 0, -0.0036f, -0.026f, 6),

		new("AI Revolution",
			"OpenAI unveils an AI capable of predicting markets with 94% accuracy. Total chaos.",
			"warning", EventRarity.Épique, 1.70f, 155f, 0, -0.0024f, -0.018f, 6),

		new("Ukraine Ceasefire",
			"Historic peace agreement signed. European markets rebound aggressively. Bullish trend.",
			"positive", EventRarity.Épique, 1.50f, 155f, 0, +0.0032f, +0.026f, 5),

		// ═══ LÉGENDAIRE (~4% total) ═══════════════════════════════════
		new("Legendary Bull Run",
			"Elon Musk buys $2B of GravCoin. Tesla accepts it. Market explodes in unprecedented frenzy.",
			"positive", EventRarity.Légendaire, 2.10f, 150f, 0, +0.0060f, +0.035f, 5), 

		new("Black Swan",
			"Collapse of 3 major US banks. Massive exodus towards crypto as a safe haven asset.",
			"positive", EventRarity.Légendaire, 2.00f, 150f, 0, +0.0070f, +0.030f, 2), 

		new("Crypto Winter",
			"USA bans retail crypto trading outright. Absolute panic. Volumes drop 90%.",
			"negative", EventRarity.Légendaire, 2.40f, 150f, 12, -0.0065f, -0.035f, 2), 

		new("Economic World War",
			"Total sanctions USA vs China. Global markets -15%. Extreme volatility across all assets.",
			"warning", EventRarity.Légendaire, 2.10f, 150f, 10, -0.0035f, -0.030f, 3), 

		new("Legal Tender Status",
			"Japan adopts GravCoin as legal currency. G7 considers global framework. Pure euphoria.",
			"positive", EventRarity.Légendaire, 2.30f, 150f, 0, +0.0060f, +0.035f, 2),

		// ═══ HYPER EVENTS (Ultra rare, ~1.2% total) ═══════════════════
		new("Hyper Bubble",
			"Hyper-deflationary protocol activated. Incurable FOMO. Total price discovery.",
			"positive", EventRarity.UltraRare, 2.50f, 180f, 0, +0.0080f, +0.050f, 2),

		new("Total Flash Crash",
			"World Bank declares all crypto illegal. Exchanges seize all assets. The end is near.",
			"negative", EventRarity.UltraRare, 3.00f, 180f, 25, -0.0085f, -0.060f, 2),
			
		// ═══ PERSONAL EVENTS (Impactent le joueur directement) ═══════
		new("Wallet Hack",
			"You clicked a suspicious link in an email. Hackers stole a significant amount of your cash balance.",
			"negative", EventRarity.Commun, 1f, 7f, 0, 0f, 0f, 15),
			
		new("Birthday Gift",
			"It's your best friend's birthday! You bought them a very expensive gift.",
			"negative", EventRarity.Commun, 1f, 7f, 0, 0f, 0f, 12),
			
		new("Lost Keys",
			"You found an old USB drive containing some forgotten USD. Lucky!",
			"positive", EventRarity.Rare, 1f, 7f, 0, 0f, 0f, 10),
			
		new("Tax Audit",
			"The IRS audited your crypto history. You had to pay a massive penalty fee in cash.",
			"negative", EventRarity.Épique, 1f, 7f, 0, 0f, 0f, 5),
			
		new("Lottery Win",
			"You bought a scratch ticket at the gas station and won!",
			"positive", EventRarity.Épique, 1f, 7f, 0, 0f, 0f, 5),
	};
	
	private double HandlePersonalEvent( string name, PlayerTrader player )
	{
		if ( player is null ) return 0;

		double cash = Math.Max( 0.0, player.Money );
		double netWorth = Math.Max( 1.0, player.NetWorth );

		// Blend wallet + net worth so events stay meaningful from early to ultra-late.
		double blendedBasis = cash * 0.35 + netWorth * 0.015;
		double basisFloor = netWorth < 50_000 ? 75.0 : netWorth < 5_000_000 ? 2_000.0 : 25_000.0;
		double basis = Math.Max( basisFloor, blendedBasis );

		double rawAmount;
		bool isLoss;
		double maxLossPctCash;

		switch ( name )
		{
			case "Wallet Hack":
				rawAmount = basis * 0.18;
				isLoss = true;
				maxLossPctCash = 0.35;
				break;
			case "Birthday Gift":
				rawAmount = basis * 0.07;
				isLoss = true;
				maxLossPctCash = 0.14;
				break;
			case "Tax Audit":
				rawAmount = basis * 0.45;
				isLoss = true;
				maxLossPctCash = 0.55;
				break;
			case "Lost Keys":
				rawAmount = basis * 0.24;
				isLoss = false;
				maxLossPctCash = 0;
				break;
			case "Lottery Win":
				rawAmount = basis * 0.95;
				isLoss = false;
				maxLossPctCash = 0;
				break;
			default:
				return 0;
		}

		if ( isLoss )
		{
			if ( cash <= 0 ) return 0;

			double minLoss = Math.Max( 15.0, basis * 0.015 );
			double maxLoss = Math.Max( minLoss, cash * maxLossPctCash );
			double targetLoss = Math.Clamp( rawAmount, minLoss, maxLoss );
			double actualLoss = Math.Min( targetLoss, cash );
			if ( actualLoss <= 0 ) return 0;
			if ( !player.DeductMoney( actualLoss ) ) return 0;
			return -actualLoss;
		}

		double gainCap = Math.Max( basisFloor * 2.0, netWorth * 0.08 + cash * 0.60 );
		double actualGain = Math.Max( 0.0, Math.Min( rawAmount, gainCap ) );
		if ( actualGain <= 0 ) return 0;
		actualGain = player.ApplyWelcomeBoostToGain( actualGain );
		player.AddMoney( actualGain );
		return actualGain;
	}
	private TimeSince _lastMarketEvent;
	private float     _nextMarketIn;
	private TimeSince _lastPersonalEvent;
	private float     _nextPersonalIn;
	private bool      _wasProxy = true;
	private int       _consecutiveDirection = 0; // 1 = up, -1 = down
	private int       _directionStreak = 0;

	protected override void OnStart()
	{
		Instance  = this;
		_wasProxy = IsProxy;
		if ( IsProxy ) return;
		_nextMarketIn   = 90f;
		_nextPersonalIn = Random.Shared.NextSingle() * 300f + 180f; // 3-8min
	}

	public static bool IsPersonalEvent( string name ) => name is "Wallet Hack" or "Birthday Gift" or "Tax Audit" or "Lost Keys" or "Lottery Win";

	protected override void OnFixedUpdate()
	{
		if ( _wasProxy && !IsProxy )
		{
			float marketRoll = Random.Shared.NextSingle() * 150f + 150f; // 2.5 - 5 min
			float personalRoll = Random.Shared.NextSingle() * 300f + 180f; // 3 - 8 min
			_nextMarketIn = marketRoll;
			_nextPersonalIn = personalRoll;
		}
		_wasProxy = IsProxy;
		if ( IsProxy ) return;

		// ── MARKET EVENTS ──
		var market = MarketManager.Instance;
		bool isMarketRunning = market is not null && market.EventTimeLeft > 0f;

		if ( isMarketRunning )
		{
			// Force the timer to stay at 0 while an event is active
			// so the gap starts counting only AFTER it finishes.
			_lastMarketEvent = 0;
		}
		else if ( _lastMarketEvent >= _nextMarketIn )
		{
			_lastMarketEvent = 0;
			TriggerRandomInternal( forcePersonal: false, marketOnly: true );
			// Minimum 2 minutes gap (120s to 180s)
			_nextMarketIn = Random.Shared.NextSingle() * 60f + 120f; 
		}

		// ── PERSONAL EVENTS ──
		if ( _lastPersonalEvent >= _nextPersonalIn )
		{
			_lastPersonalEvent = 0;
			TriggerRandomInternal( forcePersonal: true, marketOnly: false );
			_nextPersonalIn = Random.Shared.NextSingle() * 300f + 180f; // 3 - 8 min gap
		}
	}

	[Rpc.Owner]
	public void TriggerRandom() => TriggerRandomInternal( false, false );

	private void TriggerRandomInternal( bool forcePersonal, bool marketOnly )
	{
		List<WorldEvent> pool;
		if ( forcePersonal ) pool = _pool.Where( e => IsPersonalEvent( e.Name ) ).ToList();
		else if ( marketOnly ) pool = _pool.Where( e => !IsPersonalEvent( e.Name ) ).ToList();
		else pool = _pool;
		
		if ( pool.Count == 0 ) return;

		// ── Prevent >2 consecutive same-direction events ──
		if ( marketOnly || !forcePersonal )
		{
			var filteredPool = pool;
			if ( _consecutiveDirection > 0 && _directionStreak >= 2 )
				filteredPool = pool.Where( e => e.Drift <= 0 || IsPersonalEvent( e.Name ) ).ToList();
			else if ( _consecutiveDirection < 0 && _directionStreak >= 2 )
				filteredPool = pool.Where( e => e.Drift >= 0 || IsPersonalEvent( e.Name ) ).ToList();
				
			if ( filteredPool.Count > 0 ) pool = filteredPool;
		}

		int totalWeight = pool.Sum( e => e.Weight );
		int roll = Random.Shared.Next( totalWeight );
		int cumul = 0;
		WorldEvent picked = pool[0];
		foreach ( var e in pool )
		{
			cumul += e.Weight;
			if ( roll < cumul ) { picked = e; break; }
		}

		// Update streaks for market events
		if ( !IsPersonalEvent( picked.Name ) )
		{
			int dir = picked.Drift > 0 ? 1 : (picked.Drift < 0 ? -1 : 0);
			if ( dir != 0 )
			{
				if ( _consecutiveDirection == dir ) _directionStreak++;
				else { _consecutiveDirection = dir; _directionStreak = 1; }
			}
			else
			{
				_consecutiveDirection = 0;
				_directionStreak = 0;
			}
		}

		string rarityEn = picked.Rarity switch
		{
			EventRarity.Commun     => "Common",
			EventRarity.Rare       => "Rare",
			EventRarity.Épique     => "Epic",
			EventRarity.Légendaire => "Legendary",
			EventRarity.UltraRare  => "Ultra Rare",
			_ => "Common"
		};

		TriggerSpecific( picked.Name, picked.Desc, picked.Color,
			rarityEn, picked.Multiplier, picked.Duration,
			picked.TaxBonus, picked.Drift, picked.Shock );
	}

	[Rpc.Owner]
	public void TriggerSpecific( string name, string desc, string color, string rarity,
	                              float multiplier, float duration, int tax = 0, float drift = 0f, float shock = 0f )
	{
		double personalImpact = 0;
		if ( !IsPersonalEvent( name ) )
		{
			bool isHeavyEvent = string.Equals( rarity, "Legendary", StringComparison.OrdinalIgnoreCase )
				|| string.Equals( rarity, "Ultra Rare", StringComparison.OrdinalIgnoreCase );
			
			bool isMidEvent = string.Equals( rarity, "Rare", StringComparison.OrdinalIgnoreCase )
				|| string.Equals( rarity, "Epic", StringComparison.OrdinalIgnoreCase );

			float tunedMultiplier = multiplier;
			float tunedDrift = drift;
			float tunedShock = shock;
			int tunedTax = tax;

			if ( isHeavyEvent )
			{
				tunedMultiplier = 1f + (tunedMultiplier - 1f) * 0.84f;
				tunedDrift *= 0.82f;
				tunedShock *= 0.80f;
				tunedTax = (int)MathF.Round( tunedTax * 0.85f );
			}
			else if ( isMidEvent )
			{
				tunedMultiplier = 1f + (tunedMultiplier - 1f) * 0.92f;
				tunedDrift *= 0.93f;
				tunedShock *= 0.88f;
				tunedTax = (int)MathF.Round( tunedTax * 0.92f );
			}
			else
			{
				tunedMultiplier = 1f + (tunedMultiplier - 1f) * 0.96f;
				tunedDrift *= 0.97f;
				tunedShock *= 0.94f;
				tunedTax = (int)MathF.Round( tunedTax * 0.96f );
			}

			MarketManager.Instance?.ApplyEvent( name, tunedMultiplier, duration, tunedTax, tunedDrift, desc, color, rarity, tunedShock );
		}
		else
		{
			personalImpact = HandlePersonalEvent( name, PlayerTrader.Local );
		}
		BroadcastEvent( name, desc, color, rarity, duration, personalImpact );
	}

	[Rpc.Broadcast]
	public void BroadcastEvent( string name, string desc, string color, string rarity, float duration, double personalImpact = 0 )
	{
		if ( EventTriggerSound is not null )
			Sound.Play( EventTriggerSound );

		if ( IsPersonalEvent( name ) )
			EventAlertPanel.ShowPersonal( name, desc, color, rarity, personalImpact );
		else
			EventAlertPanel.Show( name, desc, color, rarity, duration );
	}
}
