using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities.Constants;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;

namespace CS2_SimpleAdmin;

public partial class CS2_SimpleAdmin
{
    internal static readonly Dictionary<CCSPlayerController, float> SpeedPlayers = [];
    internal static readonly Dictionary<CCSPlayerController, float> GravityPlayers = [];
    
    /// <summary>
    /// Executes the 'slay' command, forcing the targeted players to commit suicide.
    /// Checks player validity and permissions.
    /// </summary>
    /// <param name="caller">Player or console issuing the command.</param>
    /// <param name="command">Command details, including targets.</param>
    [RequiresPermissions("@css/slay")]
    [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnSlayCommand(CCSPlayerController? caller, CommandInfo command)
    {
        var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();

        playersToTarget.ForEach(player =>
        {
            Slay(caller, player, callerName, command);
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Performs the actual slay action on a player, with notification and logging.
    /// </summary>
    /// <param name="caller">Admin or console issuing the slay.</param>
    /// <param name="player">Target player to slay.</param>
    /// <param name="callerName">Optional name to display as the slayer.</param>
    /// <param name="command">Optional command info for logging.</param>
    internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
    {
        if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return;
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";

        // Make the player commit suicide
        player.CommitSuicide(false, true);

        // Determine message keys and arguments for the slay notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_slay_message",
                new object[] { "CALLER", player.PlayerName });

        // Display admin activity message to other players
        if (caller == null || !SilentPlayers.Contains(caller.Slot))
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }

        // Log the command and send Discord notification
        if (command == null)
            Helper.LogCommand(caller, $"css_slay {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
    }

    /// <summary>
    /// Executes the 'give' command to provide a specified weapon to targeted players.
    /// Enforces server rules for prohibited weapons.
    /// </summary>
    /// <param name="caller">Player or console issuing the command.</param>
    /// <param name="command">Command details, including targets and weapon name.</param>
    [RequiresPermissions("@css/cheats")]
    [CommandHelper(minArgs: 2, usage: "<#userid or name> <weapon>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnGiveCommand(CCSPlayerController? caller, CommandInfo command)
    {
        var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
        var weaponName = command.GetArg(2);

        // check if weapon is knife
        if (weaponName.Contains("_knife") || weaponName.Contains("bayonet"))
        {
            if (CoreConfig.FollowCS2ServerGuidelines)
            {
                command.ReplyToCommand($"Cannot Give {weaponName} because it's illegal to be given.");
                return;
            }
        }

        playersToTarget.ForEach(player =>
        {
            if (player.Connected != PlayerConnectedState.PlayerConnected)
                return;

            GiveWeapon(caller, player, weaponName, callerName, command);
        });
        
        Helper.LogCommand(caller, command);
    }

    
    /// <summary>
    /// Gives a weapon identified by name to a player, handling ambiguous matches and logging.
    /// </summary>
    /// <param name="caller">Admin issuing the command.</param>
    /// <param name="player">Target player to receive the weapon.</param>
    /// <param name="weaponName">Weapon name or partial name.</param>
    /// <param name="callerName">Optional name to display in notifications.</param>
    /// <param name="command">Optional command info for logging.</param>
    private static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, string weaponName, string? callerName = null, CommandInfo? command = null)
    {
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
        var weapons = WeaponHelper.GetWeaponsByPartialName(weaponName);

        switch (weapons.Count)
        {
            case 0:
                return;
            case > 1:
            {
                var weaponList = string.Join(", ", weapons.Select(w => w.EnumMemberValue));
                command?.ReplyToCommand($"Found weapons with a similar name: {weaponList}");
                return;
            }
        }

        // Give weapon to the player
        player.GiveNamedItem(weapons.First().EnumValue);

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weaponName}");

        // Determine message keys and arguments for the weapon give notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_give_message",
                new object[] { "CALLER", player.PlayerName, weaponName });

        // Display admin activity message to other players
        if (caller == null || !SilentPlayers.Contains(caller.Slot))
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }
    }

    /// <summary>
    /// Gives a specific weapon to a player, with notifications and logging.
    /// </summary>
    /// <param name="caller">Admin issuing the command.</param>
    /// <param name="player">Target player.</param>
    /// <param name="weapon">Weapon item object.</param>
    /// <param name="callerName">Optional caller name for notifications.</param>
    /// <param name="command">Optional command info.</param>
    internal static void GiveWeapon(CCSPlayerController? caller, CCSPlayerController player, CsItem weapon, string? callerName = null, CommandInfo? command = null)
    {
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";

        // Give weapon to the player
        player.GiveNamedItem(weapon);

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_giveweapon {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {weapon.ToString()}");

        // Determine message keys and arguments for the weapon give notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_give_message",
                new object[] { "CALLER", player.PlayerName, weapon.ToString() });

        // Display admin activity message to other players
        if (caller == null || !SilentPlayers.Contains(caller.Slot))
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }
    }

    /// <summary>
    /// Executes the 'strip' command, removing all weapons from targeted players.
    /// Checks player validity and permissions.
    /// </summary>
    /// <param name="caller">Player or console issuing the command.</param>
    /// <param name="command">Command details including targets.</param>
    [RequiresPermissions("@css/slay")]
    [CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnStripCommand(CCSPlayerController? caller, CommandInfo command)
    {
        var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();

        playersToTarget.ForEach(player =>
        {
            if (caller!.CanTarget(player))
            {
                StripWeapons(caller, player, callerName, command);
            }
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Removes all weapons from a player, with notifications and logging.
    /// </summary>
    /// <param name="caller">Admin or console issuing the strip command.</param>
    /// <param name="player">Target player.</param>
    /// <param name="callerName">Optional caller name.</param>
    /// <param name="command">Optional command info for logging.</param>
    internal static void StripWeapons(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
    {
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";

        // Check if player is valid, alive, and connected
        if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.Connected != PlayerConnectedState.PlayerConnected)
            return;

        // Strip weapons from the player
        player.RemoveWeapons();

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_strip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");

        // Determine message keys and arguments for the weapon strip notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_strip_message",
                new object[] { "CALLER", player.PlayerName });

        // Display admin activity message to other players
        if (caller == null || !SilentPlayers.Contains(caller.Slot))
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }
    }

    /// <summary>
    /// Sets health value on targeted players.
    /// </summary>
    /// <param name="caller">Admin or console issuing the command.</param>
    /// <param name="command">Command details including targets and health value.</param>
    [RequiresPermissions("@css/slay")]
    [CommandHelper(minArgs: 1, usage: "<#userid or name> <health>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnHpCommand(CCSPlayerController? caller, CommandInfo command)
    {
        int.TryParse(command.GetArg(2), out var health);

        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();

        playersToTarget.ForEach(player =>
        {
            if (caller!.CanTarget(player))
            {
                SetHp(caller, player, health, command);
            }
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Changes health of a player and logs the action.
    /// </summary>
    /// <param name="caller">Admin or console calling the method.</param>
    /// <param name="player">Target player.</param>
    /// <param name="health">Health value to set.</param>
    /// <param name="command">Optional command info.</param>
    internal static void SetHp(CCSPlayerController? caller, CCSPlayerController player, int health, CommandInfo? command = null)
    {
        if (!player.IsValid || player.IsHLTV) return;
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";

        // Set player's health
        player.SetHp(health);

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_hp {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {health}");

        // Determine message keys and arguments for the HP set notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_hp_message",
                new object[] { "CALLER", player.PlayerName });

        // Display admin activity message to other players
        if (caller == null || !SilentPlayers.Contains(caller.Slot))
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }
    }

    /// <summary>
    /// Sets movement speed on targeted players.
    /// </summary>
    /// <param name="caller">Admin or console issuing the command.</param>
    /// <param name="command">Command details including targets and speed.</param>
    [RequiresPermissions("@css/slay")]
    [CommandHelper(minArgs: 1, usage: "<#userid or name> <speed>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnSpeedCommand(CCSPlayerController? caller, CommandInfo command)
    {
        float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var speed);

        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();

        playersToTarget.ForEach(player =>
        {
            if (player.Connected != PlayerConnectedState.PlayerConnected)
                return;

            if (caller!.CanTarget(player))
            {
                SetSpeed(caller, player, speed, command);
            }
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Changes speed of a player and logs the action.
    /// </summary>
    /// <param name="caller">Admin or console calling the method.</param>
    /// <param name="player">Target player.</param>
    /// <param name="speed">Speed value to set.</param>
    /// <param name="command">Optional command info.</param>
    internal static void SetSpeed(CCSPlayerController? caller, CCSPlayerController player, float speed, CommandInfo? command = null)
    {
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";

        // Set player's speed
        player.SetSpeed(speed);
        
        if (speed == 1f)
            SpeedPlayers.Remove(player);
        else
            SpeedPlayers[player] = speed;

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_speed {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {speed}");

        // Determine message keys and arguments for the speed set notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_speed_message",
                new object[] { "CALLER", player.PlayerName });

        // Display admin activity message to other players
        if (caller == null || !SilentPlayers.Contains(caller.Slot))
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }
    }

    /// <summary>
    /// Sets gravity on targeted players.
    /// </summary>
    /// <param name="caller">Admin or console issuing the command.</param>
    /// <param name="command">Command details including targets and gravity value.</param>
    [RequiresPermissions("@css/slay")]
    [CommandHelper(minArgs: 1, usage: "<#userid or name> <gravity>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnGravityCommand(CCSPlayerController? caller, CommandInfo command)
    {
        float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var gravity);
        
        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();

        playersToTarget.ForEach(player =>
        {
            if (player.Connected != PlayerConnectedState.PlayerConnected)
                return;

            if (caller!.CanTarget(player))
            {
                SetGravity(caller, player, gravity, command);
            }
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Changes gravity of a player and logs the action.
    /// </summary>
    /// <param name="caller">Admin or console calling the method.</param>
    /// <param name="player">Target player.</param>
    /// <param name="gravity">Gravity value to set.</param>
    /// <param name="command">Optional command info.</param>
    internal static void SetGravity(CCSPlayerController? caller, CCSPlayerController player, float gravity, CommandInfo? command = null)
    {
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";

        // Set player's gravity
        player.SetGravity(gravity);
        
        if (gravity == 1f)
            GravityPlayers.Remove(player);
        else
            GravityPlayers[player] = gravity;
        
        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_gravity {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {gravity}");

        // Determine message keys and arguments for the gravity set notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_gravity_message",
                new object[] { "CALLER", player.PlayerName });

        // Display admin activity message to other players
        if (caller == null || !SilentPlayers.Contains(caller.Slot))
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }
    }

    /// <summary>
    /// Sets the money amount for the targeted players.
    /// </summary>
    /// <param name="caller">The player/admin executing the command.</param>
    /// <param name="command">The command containing target player and money value.</param>
    [RequiresPermissions("@css/slay")]
    [CommandHelper(minArgs: 1, usage: "<#userid or name> <money>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnMoneyCommand(CCSPlayerController? caller, CommandInfo command)
    {
        var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
        int.TryParse(command.GetArg(2), out var money);

        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();

        playersToTarget.ForEach(player =>
        {
            if (player.Connected != PlayerConnectedState.PlayerConnected)
                return;

            if (caller!.CanTarget(player))
            {
                SetMoney(caller, player, money, command);
            }
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Applies money value to a single targeted player and logs the operation.
    /// </summary>
    /// <param name="caller">The player/admin setting the money.</param>
    /// <param name="player">The player whose money will be set.</param>
    /// <param name="money">The value of money to set.</param>
    /// <param name="command">Optional command info for logging.</param>
    internal static void SetMoney(CCSPlayerController? caller, CCSPlayerController player, int money, CommandInfo? command = null)
    {
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";

        // Set player's money
        player.SetMoney(money);

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_money {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {money}");

        // Determine message keys and arguments for the money set notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_money_message",
                new object[] { "CALLER", player.PlayerName });

        // Display admin activity message to other players
        if (caller == null || !SilentPlayers.Contains(caller.Slot))
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }
    }

    /// <summary>
    /// Applies damage as a slap effect to the targeted players.
    /// </summary>
    /// <param name="caller">The player/admin executing the slap command.</param>
    /// <param name="command">The command including targets and optional damage value.</param>
    [RequiresPermissions("@css/slay")]
    [CommandHelper(minArgs: 1, usage: "<#userid or name> [damage]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnSlapCommand(CCSPlayerController? caller, CommandInfo command)
    {
        var damage = 0;

        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player.IsValid && player is { IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();

        if (command.ArgCount >= 2)
        {
            int.TryParse(command.GetArg(2), out damage);
        }

        playersToTarget.ForEach(player =>
        {
            if (player.Connected != PlayerConnectedState.PlayerConnected)
                return;

            if (caller!.CanTarget(player))
            {
                Slap(caller, player, damage, command);
            }
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Applies slap damage to a specific player with notifications and logging.
    /// </summary>
    /// <param name="caller">The player/admin applying the slap effect.</param>
    /// <param name="player">The target player to slap.</param>
    /// <param name="damage">The damage amount to apply.</param>
    /// <param name="command">Optional command info for logging.</param>
    internal static void Slap(CCSPlayerController? caller, CCSPlayerController player, int damage, CommandInfo? command = null)
    {
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
        
        // Apply slap damage to the player
        player.Pawn.Value?.Slap(damage);

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_slap {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {damage}");
        
        // Determine message key and arguments for the slap notification
        var (activityMessageKey, adminActivityArgs) =
            ("sa_admin_slap_message",
                new object[] { "CALLER", player.PlayerName });

        // Display admin activity message to other players
        if (caller != null && SilentPlayers.Contains(caller.Slot)) return;

        if (_localizer != null)
        {
            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
        }
    }

    /// <summary>
    /// Changes the team of targeted players with optional kill on switch.
    /// </summary>
    /// <param name="caller">The player/admin issuing the command.</param>
    /// <param name="command">The command containing targets, team info, and optional kill flag.</param>
    [RequiresPermissions("@css/kick")]
    [CommandHelper(minArgs: 2, usage: "<#userid or name> [<ct/tt/spec>] [-k]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
    public void OnTeamCommand(CCSPlayerController? caller, CommandInfo command)
    {
        var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
        var teamName = command.GetArg(2).ToLower();
        string _teamName;
        var teamNum = CsTeam.Spectator;

        var targets = GetTarget(command);
        if (targets == null) return;

        var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();

        switch (teamName)
        {
            case "ct":
            case "counterterrorist":
                teamNum = CsTeam.CounterTerrorist;
                _teamName = "CT";
                break;

            case "t":
            case "tt":
            case "terrorist":
                teamNum = CsTeam.Terrorist;
                _teamName = "TT";
                break;

            case "swap":
                _teamName = "SWAP";
                break;

            default:
                teamNum = CsTeam.Spectator;
                _teamName = "SPEC";
                break;
        }

        var kill = command.GetArg(3).ToLower().Equals("-k");

        playersToTarget.ForEach(player =>
        {
            ChangeTeam(caller, player, _teamName, teamNum, kill, command);
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Changes the team of a player with various conditions and logs the operation.
    /// </summary>
    /// <param name="caller">The player/admin issuing the change.</param>
    /// <param name="player">The target player.</param>
    /// <param name="teamName">Team name string.</param>
    /// <param name="teamNum">Team enumeration value.</param>
    /// <param name="kill">If true, kills player on team change.</param>
    /// <param name="command">Optional command info for logging.</param>
    internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null)
    {
        // Check if the player is valid and connected
        if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected)
            return;

        // Ensure the caller can target the player
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";

        // Change team based on the provided teamName and conditions
        if (!teamName.Equals("swap", StringComparison.OrdinalIgnoreCase))
        {
            if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && teamNum != CsTeam.Spectator && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
                player.SwitchTeam(teamNum);
            else
                player.ChangeTeam(teamNum);
        }
        else
        {
            if (player.TeamNum != (byte)CsTeam.Spectator)
            {
                var _teamNum = (CsTeam)player.TeamNum == CsTeam.Terrorist ? CsTeam.CounterTerrorist : CsTeam.Terrorist;
                teamName = _teamNum == CsTeam.Terrorist ? "TT" : "CT";
                if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE && !kill && Instance.Config.OtherSettings.TeamSwitchType == 1)
                    player.SwitchTeam(_teamNum);
                else
                    player.ChangeTeam(_teamNum);
            }
        }

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_team {player.PlayerName} {teamName}");

        // Determine message key and arguments for the team change notification
        var activityMessageKey = "sa_admin_team_message";
        var adminActivityArgs = new object[] { "CALLER", player.PlayerName, teamName };

        // Display admin activity message to other players
        if (caller != null && SilentPlayers.Contains(caller.Slot)) return;

        Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
    }

    /// <summary>
    /// Renames targeted players to a new name.
    /// </summary>
    /// <param name="caller">The admin issuing the rename command.</param>
    /// <param name="command">The command including targets and new name.</param>
    [CommandHelper(1, "<#userid or name> <new name>")]
    [RequiresPermissions("@css/kick")]
    public void OnRenameCommand(CCSPlayerController? caller, CommandInfo command)
    {
        // Set default caller name if not provided
        var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;

        // Get the new name from the command arguments
        var newName = command.GetArg(2);

        // Check if the new name is valid
        if (string.IsNullOrEmpty(newName))
            return;

        // Retrieve the targets based on the command
        var targets = GetTarget(command);
        if (targets == null) return;

        // Filter out valid players from the targets
        var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();

        // Log the command
        Helper.LogCommand(caller, command);

        // Process each player to rename
        playersToTarget.ForEach(player =>
        {
            // Check if the player is connected and can be targeted
            if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player))
                return;
            
            // Determine message key and arguments for the rename notification
            var activityMessageKey = "sa_admin_rename_message";
            var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };

            // Display admin activity message to other players
            if (caller != null && SilentPlayers.Contains(caller.Slot)) return;

            Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
            
            // Rename the player
            player.Rename(newName);
        });
    }

    /// <summary>
    /// Renames permamently targeted players to a new name.
    /// </summary>
    /// <param name="caller">The admin issuing the pre-rename command.</param>
    /// <param name="command">The command containing targets and new alias.</param>
    [CommandHelper(1, "<#userid or name> <new name>")]
    [RequiresPermissions("@css/ban")]
    public void OnPrenameCommand(CCSPlayerController? caller, CommandInfo command)
    {
        // Set default caller name if not provided
        var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;

        // Get the new name from the command arguments
        var newName = command.GetArg(2);

        // Retrieve the targets based on the command
        var targets = GetTarget(command);
        if (targets == null) return;

        // Filter out valid players from the targets
        var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();

        // Log the command
        Helper.LogCommand(caller, command);

        // Process each player to rename
        playersToTarget.ForEach(player =>
        {
            // Check if the player is connected and can be targeted
            if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player))
                return;
            
            // Determine message key and arguments for the rename notification
            var activityMessageKey = "sa_admin_rename_message";
            var adminActivityArgs = new object[] { "CALLER", player.PlayerName, newName };

            // Display admin activity message to other players
            if (caller != null && !SilentPlayers.Contains(caller.Slot))
            {
                Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
            }
            
            // Determine if the new name is valid and update the renamed players list
            if (!string.IsNullOrEmpty(newName))
            {
                RenamedPlayers[player.SteamID] = newName;
                player.Rename(newName);
            }
            else
            {
                RenamedPlayers.Remove(player.SteamID);
            }
        });
    }

    /// <summary>
    /// Respawns targeted players, restoring their state.
    /// </summary>
    /// <param name="caller">The admin or player issuing respawn.</param>
    /// <param name="command">The command including target players.</param>
    [CommandHelper(1, "<#userid or name>")]
    [RequiresPermissions("@css/cheats")]
    public void OnRespawnCommand(CCSPlayerController? caller, CommandInfo command)
    {
        var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;

        var targets = GetTarget(command);
        if (targets == null) return;
        var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();

        playersToTarget.ForEach(player =>
        {
            if (player.Connected != PlayerConnectedState.PlayerConnected)
                return;

            if (caller!.CanTarget(player))
            {
                Respawn(caller, player, callerName, command);
            }
        });
        
        Helper.LogCommand(caller, command);
    }

    /// <summary>
    /// Respawns a specified player and updates admin notifications.
    /// </summary>
    /// <param name="caller">Admin or player executing respawn.</param>
    /// <param name="player">Player to respawn.</param>
    /// <param name="callerName">Optional admin name.</param>
    /// <param name="command">Optional command info.</param>
    internal static void Respawn(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
    {
        // Check if the caller can target the player
        if (!caller.CanTarget(player)) return;

        // Set default caller name if not provided
        callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;

        // Ensure the player's pawn is valid before attempting to respawn
        if (_cBasePlayerControllerSetPawnFunc == null || player.PlayerPawn.Value == null || !player.PlayerPawn.IsValid) return;

        // Perform the respawn operation
        var playerPawn = player.PlayerPawn.Value;
        _cBasePlayerControllerSetPawnFunc.Invoke(player, playerPawn, true, false);
        VirtualFunction.CreateVoid<CCSPlayerController>(player.Handle, GameData.GetOffset("CCSPlayerController_Respawn"))(player);
        
        if (player.UserId.HasValue && PlayersInfo.TryGetValue(player.SteamID, out var value) && value.DiePosition != null)
            playerPawn.Teleport(value.DiePosition?.Position, value.DiePosition?.Angle);

        // Log the command
        if (command == null)
            Helper.LogCommand(caller, $"css_respawn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");

        // Determine message key and arguments for the respawn notification
        var activityMessageKey = "sa_admin_respawn_message";
        var adminActivityArgs = new object[] { "CALLER", player.PlayerName };

        // Display admin activity message to other players
        if (caller != null && SilentPlayers.Contains(caller.Slot)) return;

        Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
    }

    /// <summary>
    /// Teleports targeted player(s) to another player's location.
    /// </summary>
    /// <param name="caller">Admin issuing teleport command.</param>
    /// <param name="command">Command containing teleport targets and destination.</param>
    [CommandHelper(1, "<#userid or name> [#userid or name]")]
    [RequiresPermissions("@css/kick")]
    public void OnGotoCommand(CCSPlayerController? caller, CommandInfo command)
    {
        IEnumerable<CCSPlayerController> playersToTeleport;
        CCSPlayerController? destinationPlayer;

        var targets = GetTarget(command);

        if (command.ArgCount < 3)
        {
            if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
                return;

            if (targets == null || targets.Count() != 1)
                return;

            destinationPlayer = targets.Players.FirstOrDefault(p =>
                p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });

            if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null)
                return;

            playersToTeleport = [caller];
        }
        else
        {
            var destination = GetTarget(command, 2);
            if (targets == null || destination == null || destination.Count() != 1)
                return;

            destinationPlayer = destination.Players.FirstOrDefault(p =>
                p is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });

            if (destinationPlayer == null)
                return;

            playersToTeleport = targets.Players
                .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
                .ToList();

            if (!playersToTeleport.Any())
                return;
        }

        // Log command
        Helper.LogCommand(caller, command);

        foreach (var player in playersToTeleport)
        {
            if (player.PlayerPawn?.Value == null || destinationPlayer?.PlayerPawn?.Value == null)
                continue;

            player.TeleportPlayer(destinationPlayer);

            player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
            player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
            Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
            Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");

            destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
            destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
            Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
            Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");

            AddTimer(4, () =>
            {
                if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
                {
                    player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
                    player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
                    Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
                    Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
                }

                if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
                {
                    destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
                    destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
                    Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
                    Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
                }
            });

            if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
            {
                Helper.ShowAdminActivity("sa_admin_tp_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
            }
        }
    }
    
    /// <summary>
    /// Brings targeted player(s) to the caller or specified destination player's location.
    /// </summary>
    /// <param name="caller">Player issuing the bring command.</param>
    /// <param name="command">Command containing the destination and targets.</param>
    [CommandHelper(1, "<#destination or name> [#userid or name...]")]
    [RequiresPermissions("@css/kick")]
    public void OnBringCommand(CCSPlayerController? caller, CommandInfo command)
    {
        IEnumerable<CCSPlayerController> playersToTeleport;
        CCSPlayerController? destinationPlayer;

        if (command.ArgCount < 3)
        {
            if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
                return;

            var targets = GetTarget(command);
            if (targets == null || !targets.Any())
                return;

            destinationPlayer = caller;

            playersToTeleport = targets.Players
                .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
                .ToList();
        }
        else
        {
            var destination = GetTarget(command);
            if (destination == null || destination.Count() != 1)
                return;

            destinationPlayer = destination.Players.FirstOrDefault(p =>
                p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });

            if (destinationPlayer == null)
                return;

            // Rest args = targets to teleport
            var targets = GetTarget(command, 2);
            if (targets == null || !targets.Any())
                return;

            playersToTeleport = targets.Players
                .Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p))
                .ToList();
        }

        if (destinationPlayer == null || !playersToTeleport.Any())
            return;

        // Log command
        Helper.LogCommand(caller, command);

        foreach (var player in playersToTeleport)
        {
            if (player.PlayerPawn?.Value == null || destinationPlayer.PlayerPawn?.Value == null)
                continue;

            // Teleport
            player.TeleportPlayer(destinationPlayer);

            player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
            player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
            Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
            Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");

            destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
            destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
            Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
            Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");

            AddTimer(4, () =>
            {
                if (player is { IsValid: true, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
                {
                    player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
                    player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
                    Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
                    Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
                }

                if (destinationPlayer.IsValid && destinationPlayer.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
                {
                    destinationPlayer.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
                    destinationPlayer.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
                    Utilities.SetStateChanged(destinationPlayer, "CCollisionProperty", "m_CollisionGroup");
                    Utilities.SetStateChanged(destinationPlayer, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
                }
            });

            if (caller != null && !SilentPlayers.Contains(caller.Slot) && _localizer != null)
            {
                Helper.ShowAdminActivity("sa_admin_bring_message", player.PlayerName, false, "CALLER", destinationPlayer.PlayerName);
            }
        }
    }

    // [CommandHelper(1, "<#userid or name> [#userid or name]")]
    // [RequiresPermissions("@css/kick")]
    // public void OnBringCommand(CCSPlayerController? caller, CommandInfo command)
    // {
    //     // Check if the caller is valid and has a live pawn
    //     if (caller == null || caller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) 
    //         return;
    //
    //     // Get the target players
    //     var targets = GetTarget(command);
    //     if (targets == null || targets.Count() > 1) return;
    //
    //     var playersToTarget = targets.Players
    //         .Where(player => player is { IsValid: true, IsHLTV: false })
    //         .ToList();
    //
    //     // Log the command
    //     Helper.LogCommand(caller, command);
    //
    //     // Process each player to teleport
    //     foreach (var player in playersToTarget.Where(player => player is { Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).Where(caller.CanTarget))
    //     {
    //         if (caller.PlayerPawn.Value == null || player.PlayerPawn.Value == null)
    //             continue;
    //
    //         // Teleport the player to the caller and toggle noclip
    //         player.TeleportPlayer(caller);
    //         // caller.PlayerPawn.Value.ToggleNoclip();
    //         
    //         caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
    //         caller.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
    //         
    //         Utilities.SetStateChanged(caller, "CCollisionProperty", "m_CollisionGroup");
    //         Utilities.SetStateChanged(caller, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
    //         
    //         player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
    //         player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_DISSOLVING;
    //         
    //         Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
    //         Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
    //
    //         // Set a timer to toggle collision back after 4 seconds
    //         AddTimer(4, () =>
    //         {
    //             if (!player.IsValid || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE)
    //                 return;
    //             
    //             caller.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
    //             caller.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
    //         
    //             Utilities.SetStateChanged(caller, "CCollisionProperty", "m_CollisionGroup");
    //             Utilities.SetStateChanged(caller, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
    //             
    //             player.PlayerPawn.Value.Collision.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
    //             player.PlayerPawn.Value.Collision.CollisionAttribute.CollisionGroup = (byte)CollisionGroup.COLLISION_GROUP_PLAYER;
    //         
    //             Utilities.SetStateChanged(player, "CCollisionProperty", "m_CollisionGroup");
    //             Utilities.SetStateChanged(player, "VPhysicsCollisionAttribute_t", "m_nCollisionGroup");
    //         });
    //
    //         // Prepare message key and arguments for the bring notification
    //         var activityMessageKey = "sa_admin_bring_message";
    //         var adminActivityArgs = new object[] { "CALLER", player.PlayerName };
    //
    //         // Show admin activity
    //         if (!SilentPlayers.Contains(caller.Slot) && _localizer != null)
    //         {
    //             Helper.ShowAdminActivity(activityMessageKey, caller.PlayerName, false, adminActivityArgs);
    //         }
    //     }
    // }
}