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

namespace GravCoin;

public static class SandMoneyAdminCommands
{
	private const ulong AllowedAdminSteamId = 76561198868596556UL;
	private const string AdminPermission = "sandmoney.admin";

	private static bool IsAuthorizedAdmin( Connection caller )
	{
		if ( caller is null )
			return (ulong)Game.SteamId == AllowedAdminSteamId;

		return (ulong)caller.SteamId == AllowedAdminSteamId || caller.HasPermission( AdminPermission );
	}

	private static bool EnsureAuthorized( Connection caller, string commandName )
	{
		if ( !Networking.IsHost )
		{
			Log.Warning( $"[SandMoney] {commandName} requires host authority." );
			return false;
		}

		if ( !IsAuthorizedAdmin( caller ) )
		{
			Log.Warning( $"[SandMoney] {commandName} denied: caller is not admin." );
			return false;
		}

		return true;
	}

	private static bool TryParseSteamId( string steamIdText, out ulong steamId )
	{
		return ulong.TryParse( steamIdText, NumberStyles.Integer, CultureInfo.InvariantCulture, out steamId );
	}

	private static Connection FindConnectionBySteamId( ulong steamId )
	{
		return Connection.All.FirstOrDefault( c => (ulong)c.SteamId == steamId );
	}

	private static IEnumerable<PlayerTrader> GetAllTraders()
	{
		return Game.ActiveScene?.GetAllComponents<PlayerTrader>() ?? Enumerable.Empty<PlayerTrader>();
	}

	private static PlayerTrader FindTraderBySteamId( ulong steamId )
	{
		return GetAllTraders()
			.FirstOrDefault( p => p.Network?.Owner is not null && (ulong)p.Network.Owner.SteamId == steamId );
	}

	private static string ConnLabel( Connection c )
	{
		if ( c is null ) return "<null>";
		string role = c.IsHost ? "HOST" : "CLIENT";
		return $"[{role}] {c.DisplayName} ({(ulong)c.SteamId})";
	}

	private static string TraderLabel( PlayerTrader trader )
	{
		if ( trader is null ) return "<null>";
		var owner = trader.Network?.Owner;
		if ( owner is null )
			return $"{trader.PlayerName} (local)";

		return $"{owner.DisplayName} ({(ulong)owner.SteamId})";
	}

	private static int KickMatching( Func<Connection, bool> predicate, string reason )
	{
		int kicked = 0;
		foreach ( var connection in Connection.All )
		{
			if ( connection is null )
				continue;
			if ( connection.IsHost )
				continue;
			if ( !predicate( connection ) )
				continue;

			connection.Kick( reason );
			kicked++;
		}

		return kicked;
	}

	private static bool EnsureKickReason( string reason, string usage )
	{
		if ( !string.IsNullOrWhiteSpace( reason ) )
			return true;

		Log.Warning( $"[SandMoney] Usage: {usage}" );
		return false;
	}

	private static bool TryAdjustTraderMoney( PlayerTrader trader, double delta )
	{
		if ( trader is null ) return false;
		if ( double.IsNaN( delta ) || double.IsInfinity( delta ) ) return false;
		if ( Math.Abs( delta ) < 0.0001 ) return true;

		// Uses owner RPC path so it can apply to the target player owner.
		// Do not force save from admin commands; autosave handles persistence.
		trader.CmdDebugAddMoney( delta );
		return true;
	}

	[ConCmd( "sbmoney_admin_help", ConVarFlags.Server )]
	public static void Help( Connection caller )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_admin_help" ) ) return;

		Log.Info( "[SandMoney] Admin commands:" );
		Log.Info( "[SandMoney] sbmoney_admin_whoami" );
		Log.Info( "[SandMoney] sbmoney_admin_players" );
		Log.Info( "[SandMoney] sbmoney_kick <steamid> <reason>" );
		Log.Info( "[SandMoney] sbmoney_kick_reason <steamid> <reason>" );
		Log.Info( "[SandMoney] sbmoney_kick_name <name_part> <reason>" );
		Log.Info( "[SandMoney] sbmoney_kick_all <reason>" );
		Log.Info( "[SandMoney] sbmoney_kick_all_except <steamid> <reason>" );
		Log.Info( "[SandMoney] sbmoney_reward <steamid> <value>" );
		Log.Info( "[SandMoney] sbmoney_reward_all <value>" );
		Log.Info( "[SandMoney] sbmoney_setmoney <steamid> <value>" );
		Log.Info( "[SandMoney] sbmoney_setmoney_all <value>" );
		Log.Info( "[SandMoney] sbmoney_zero <steamid>" );
		Log.Info( "[SandMoney] sbmoney_reset <steamid>" );
		Log.Info( "[SandMoney] sbmoney_reset_all" );
	}

	[ConCmd( "sbmoney_admin_whoami", ConVarFlags.Server )]
	public static void WhoAmI( Connection caller )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_admin_whoami" ) ) return;

		string callerLabel = caller is null ? "<null>" : ConnLabel( caller );
		Log.Info( $"[SandMoney] Local SteamId: {(ulong)Game.SteamId}" );
		Log.Info( $"[SandMoney] IsHost: {Networking.IsHost}" );
		Log.Info( $"[SandMoney] Caller: {callerLabel}" );
		Log.Info( $"[SandMoney] Connection.Local: {ConnLabel( Connection.Local )}" );
		Log.Info( $"[SandMoney] Connection.Host: {ConnLabel( Connection.Host )}" );
	}

	[ConCmd( "sbmoney_admin_players", ConVarFlags.Server )]
	public static void Players( Connection caller )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_admin_players" ) ) return;

		var ordered = Connection.All
			.OrderByDescending( c => c.IsHost )
			.ThenBy( c => c.DisplayName, StringComparer.OrdinalIgnoreCase )
			.ToList();

		Log.Info( $"[SandMoney] Connected players: {ordered.Count}" );
		foreach ( var connection in ordered )
			Log.Info( $"[SandMoney] {ConnLabel( connection )}" );
	}

	[ConCmd( "sbmoney_kick", ConVarFlags.Server )]
	public static void Kick( Connection caller, string steamIdText, string reason )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_kick" ) ) return;
		if ( !EnsureKickReason( reason, "sbmoney_kick <steamid> <reason>" ) ) return;

		if ( !TryParseSteamId( steamIdText, out var steamId ) )
		{
			Log.Warning( "[SandMoney] Invalid steamid. Usage: sbmoney_kick <steamid> <reason>" );
			return;
		}

		var target = FindConnectionBySteamId( steamId );
		if ( target is null )
		{
			Log.Warning( $"[SandMoney] No connection found for steamid {steamId}." );
			return;
		}

		if ( target.IsHost )
		{
			Log.Warning( "[SandMoney] Refused to kick host connection." );
			return;
		}

		target.Kick( reason );
		Log.Info( $"[SandMoney] Kicked {target.DisplayName} ({steamId})." );
	}

	[ConCmd( "sbmoney_kick_reason", ConVarFlags.Server )]
	public static void KickReason( Connection caller, string steamIdText, string reason )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_kick_reason" ) ) return;
		if ( !EnsureKickReason( reason, "sbmoney_kick_reason <steamid> <reason>" ) ) return;

		if ( !TryParseSteamId( steamIdText, out var steamId ) )
		{
			Log.Warning( "[SandMoney] Invalid steamid. Usage: sbmoney_kick_reason <steamid> <reason>" );
			return;
		}

		var target = FindConnectionBySteamId( steamId );
		if ( target is null )
		{
			Log.Warning( $"[SandMoney] No connection found for steamid {steamId}." );
			return;
		}

		if ( target.IsHost )
		{
			Log.Warning( "[SandMoney] Refused to kick host connection." );
			return;
		}

		target.Kick( reason );
		Log.Info( $"[SandMoney] Kicked {target.DisplayName} ({steamId}) with custom reason." );
	}

	[ConCmd( "sbmoney_kick_name", ConVarFlags.Server )]
	public static void KickName( Connection caller, string namePart, string reason )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_kick_name" ) ) return;
		if ( !EnsureKickReason( reason, "sbmoney_kick_name <name_part> <reason>" ) ) return;

		if ( string.IsNullOrWhiteSpace( namePart ) )
		{
			Log.Warning( "[SandMoney] Usage: sbmoney_kick_name <name_part> <reason>" );
			return;
		}

		var matches = Connection.All
			.Where( c => !c.IsHost && c.DisplayName.Contains( namePart, StringComparison.OrdinalIgnoreCase ) )
			.ToList();

		if ( matches.Count == 0 )
		{
			Log.Warning( $"[SandMoney] No player matches '{namePart}'." );
			return;
		}

		if ( matches.Count > 1 )
		{
			Log.Warning( $"[SandMoney] Ambiguous name '{namePart}'. Matches: {matches.Count}" );
			foreach ( var match in matches )
				Log.Info( $"[SandMoney] - {ConnLabel( match )}" );
			return;
		}

		var target = matches[0];
		target.Kick( reason );
		Log.Info( $"[SandMoney] Kicked {ConnLabel( target )}." );
	}

	[ConCmd( "sbmoney_kick_all", ConVarFlags.Server )]
	public static void KickAll( Connection caller, string reason )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_kick_all" ) ) return;
		if ( !EnsureKickReason( reason, "sbmoney_kick_all <reason>" ) ) return;

		int kicked = KickMatching( _ => true, reason );
		Log.Info( $"[SandMoney] Kicked {kicked} client(s)." );
	}

	[ConCmd( "sbmoney_kick_all_except", ConVarFlags.Server )]
	public static void KickAllExcept( Connection caller, string steamIdToKeepText, string reason )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_kick_all_except" ) ) return;
		if ( !EnsureKickReason( reason, "sbmoney_kick_all_except <steamid> <reason>" ) ) return;

		if ( !TryParseSteamId( steamIdToKeepText, out var keepSteamId ) )
		{
			Log.Warning( "[SandMoney] Invalid steamid. Usage: sbmoney_kick_all_except <steamid> <reason>" );
			return;
		}

		int kicked = KickMatching( c => (ulong)c.SteamId != keepSteamId, reason );
		Log.Info( $"[SandMoney] Kicked {kicked} client(s), kept {keepSteamId}." );
	}

	[ConCmd( "sbmoney_reward", ConVarFlags.Server )]
	public static void Reward( Connection caller, string steamIdText, double value )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_reward" ) ) return;

		if ( !TryParseSteamId( steamIdText, out var steamId ) )
		{
			Log.Warning( "[SandMoney] Invalid steamid. Usage: sbmoney_reward <steamid> <value>" );
			return;
		}

		if ( value <= 0 || double.IsNaN( value ) || double.IsInfinity( value ) )
		{
			Log.Warning( "[SandMoney] Reward value must be a positive finite number." );
			return;
		}

		var trader = FindTraderBySteamId( steamId );
		if ( trader is null )
		{
			Log.Warning( $"[SandMoney] No PlayerTrader found for steamid {steamId}." );
			return;
		}

		if ( !TryAdjustTraderMoney( trader, value ) )
		{
			Log.Warning( "[SandMoney] Could not apply reward." );
			return;
		}

		Log.Info( $"[SandMoney] Rewarded {value:F2} to {TraderLabel( trader )}." );
	}

	[ConCmd( "sbmoney_reward_all", ConVarFlags.Server )]
	public static void RewardAll( Connection caller, double value )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_reward_all" ) ) return;

		if ( value <= 0 || double.IsNaN( value ) || double.IsInfinity( value ) )
		{
			Log.Warning( "[SandMoney] Reward value must be a positive finite number." );
			return;
		}

		int changed = 0;
		foreach ( var trader in GetAllTraders() )
		{
			if ( TryAdjustTraderMoney( trader, value ) )
				changed++;
		}

		Log.Info( $"[SandMoney] Rewarded {value:F2} to {changed} player(s)." );
	}

	[ConCmd( "sbmoney_setmoney", ConVarFlags.Server )]
	public static void SetMoney( Connection caller, string steamIdText, double targetValue )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_setmoney" ) ) return;

		if ( !TryParseSteamId( steamIdText, out var steamId ) )
		{
			Log.Warning( "[SandMoney] Invalid steamid. Usage: sbmoney_setmoney <steamid> <value>" );
			return;
		}

		if ( double.IsNaN( targetValue ) || double.IsInfinity( targetValue ) || targetValue < 0 )
		{
			Log.Warning( "[SandMoney] Target value must be a non-negative finite number." );
			return;
		}

		var trader = FindTraderBySteamId( steamId );
		if ( trader is null )
		{
			Log.Warning( $"[SandMoney] No PlayerTrader found for steamid {steamId}." );
			return;
		}

		double delta = targetValue - trader.Money;
		if ( !TryAdjustTraderMoney( trader, delta ) )
		{
			Log.Warning( "[SandMoney] Could not set money." );
			return;
		}

		Log.Info( $"[SandMoney] Set money for {TraderLabel( trader )}: {trader.Money:F2} -> {targetValue:F2}." );
	}

	[ConCmd( "sbmoney_setmoney_all", ConVarFlags.Server )]
	public static void SetMoneyAll( Connection caller, double targetValue )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_setmoney_all" ) ) return;

		if ( double.IsNaN( targetValue ) || double.IsInfinity( targetValue ) || targetValue < 0 )
		{
			Log.Warning( "[SandMoney] Target value must be a non-negative finite number." );
			return;
		}

		int changed = 0;
		foreach ( var trader in GetAllTraders() )
		{
			double delta = targetValue - trader.Money;
			if ( TryAdjustTraderMoney( trader, delta ) )
				changed++;
		}

		Log.Info( $"[SandMoney] Set money to {targetValue:F2} for {changed} player(s)." );
	}

	[ConCmd( "sbmoney_zero", ConVarFlags.Server )]
	public static void Zero( Connection caller, string steamIdText )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_zero" ) ) return;
		SetMoney( caller, steamIdText, 0.0 );
	}

	[ConCmd( "sbmoney_reset", ConVarFlags.Server )]
	public static void Reset( Connection caller, string steamIdText )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_reset" ) ) return;

		if ( !TryParseSteamId( steamIdText, out var steamId ) )
		{
			Log.Warning( "[SandMoney] Invalid steamid. Usage: sbmoney_reset <steamid>" );
			return;
		}

		var trader = FindTraderBySteamId( steamId );
		if ( trader is null )
		{
			Log.Warning( $"[SandMoney] No PlayerTrader found for steamid {steamId}." );
			return;
		}

		trader.CmdResetProfileNoSave();
		Log.Info( $"[SandMoney] Reset profile requested (no forced save) for {TraderLabel( trader )}." );
	}

	[ConCmd( "sbmoney_reset_all", ConVarFlags.Server )]
	public static void ResetAll( Connection caller )
	{
		if ( !EnsureAuthorized( caller, "sbmoney_reset_all" ) ) return;

		int reset = 0;
		foreach ( var trader in GetAllTraders() )
		{
			trader.CmdResetProfileNoSave();
			reset++;
		}

		Log.Info( $"[SandMoney] Reset profile requested (no forced save) for {reset} player(s)." );
	}
}
