using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Database.Models;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using static Data.Models.Client.EFClient;

namespace SharedLibraryCore.Commands
{
    /// <summary>
    /// Quits IW4MAdmin
    /// </summary>
    public class QuitCommand : Command
    {
        public QuitCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "quit";
            Description = _translationLookup["COMMANDS_QUIT_DESC"];
            Alias = "q";
            Permission = Permission.Owner;
            RequiresTarget = false;
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            E.Owner.Manager.Stop();
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Restarts IW4MAdmin
    /// </summary>
    public class RestartCommand : Command
    {
        public RestartCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "restart";
            Description = _translationLookup["COMMANDS_RESTART_DESC"];
            Alias = "res";
            Permission = Permission.Owner;
            RequiresTarget = false;
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            E.Owner.Manager.Restart();
            E.Origin.Tell(_translationLookup["COMMANDS_RESTART_SUCCESS"]);
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Claims ownership of the server
    /// </summary>
    public class OwnerCommand : Command
    {
        public OwnerCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "owner";
            Description = _translationLookup["COMMANDS_OWNER_DESC"];
            Alias = "iamgod";
            Permission = Permission.User;
            RequiresTarget = false;
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            // they're trying to set the console's permission level... sigh... 
            if (E.Origin.Level == Permission.Console)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_OWNER_IDIOT"]);
                return;
            }

            if (await (E.Owner.Manager.GetClientService() as ClientService).GetOwnerCount() == 0 &&
                !E.Origin.SetLevel(EFClient.Permission.Owner, Utilities.IW4MAdminClient(E.Owner)).Failed)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_OWNER_SUCCESS"]);
            }
            else
            {
                E.Origin.Tell(_translationLookup["COMMANDS_OWNER_FAIL"]);
            }
        }
    }

    /// <summary>
    /// Warns given client for reason
    /// </summary>
    public class WarnCommand : Command
    {
        private readonly ApplicationConfiguration _appConfig;
        public WarnCommand(ApplicationConfiguration appConfig, CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "warn";
            Description = _translationLookup["COMMANDS_WARN_DESC"];
            Alias = "w";
            Permission = Permission.Trusted;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_REASON"],
                    Required = true
                }
            };
            _appConfig = appConfig;
        }

        public override Task ExecuteAsync(GameEvent gameEvent)
        {
            var reason = gameEvent.Data.FindRuleForReason(_appConfig, gameEvent.Owner);
            if (gameEvent.Target.Warn(reason, gameEvent.Origin).Failed)
            {
                gameEvent.Origin.Tell(_translationLookup["COMMANDS_WARN_FAIL"].FormatExt(gameEvent.Target.Name));
            }

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Clears all warnings for given client
    /// </summary>
    public class WarnClearCommand : Command
    {
        public WarnClearCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "warnclear";
            Description = _translationLookup["COMMANDS_WARNCLEAR_DESC"];
            Alias = "wc";
            Permission = Permission.Trusted;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                }
            };
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            if (!E.Target.WarnClear(E.Origin).Failed)
            {
                E.Owner.Broadcast(_translationLookup["COMMANDS_WARNCLEAR_SUCCESS"].FormatExt(E.Target.Name));
            }

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Kicks client for given reason
    /// </summary>
    public class KickCommand : Command
    {
        private readonly ApplicationConfiguration _appConfig;
        
        public KickCommand(ApplicationConfiguration appConfig, CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "kick";
            Description = _translationLookup["COMMANDS_KICK_DESC"];
            Alias = "k";
            Permission = Permission.Moderator;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_REASON"],
                    Required = true
                }
            };
            _appConfig = appConfig;
        }

        public override async Task ExecuteAsync(GameEvent gameEvent)
        {
            var reason = gameEvent.Data.FindRuleForReason(_appConfig, gameEvent.Owner);
            switch ((await gameEvent.Target.Kick(reason, gameEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken)).FailReason)
            {
                case GameEvent.EventFailReason.None:
                    gameEvent.Origin.Tell(_translationLookup["COMMANDS_KICK_SUCCESS"].FormatExt(gameEvent.Target.Name));
                    break;
                case GameEvent.EventFailReason.Exception:
                    gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
                    break;
                default:
                    gameEvent.Origin.Tell(_translationLookup["COMMANDS_KICK_FAIL"].FormatExt(gameEvent.Target.Name));
                    break;
            }
        }
    }

    /// <summary>
    /// Prints out a message to all clients on the server
    /// </summary>
    public class SayCommand : Command
    {
        public SayCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "say";
            Description = _translationLookup["COMMANDS_SAY_DESC"];
            Alias = "s";
            Permission = Permission.Moderator;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
                    Required = true
                }
            };
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            E.Owner.Broadcast($"{(E.Owner.GameName == Server.Game.IW4 ? "^:" : "")}{E.Origin.Name} - ^6{E.Data}^7", E.Origin);
            E.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Prints out a message to all clients on all servers
    /// </summary>
    public class SayAllCommand : Command
    {
        public SayAllCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "sayall";
            Description = _translationLookup["COMMANDS_SAY_ALL_DESC"];
            Alias = "sa";
            Permission = Permission.Moderator;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
                    Required = true
                }
            };
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            string message = _translationLookup["COMMANDS_SAY_ALL_MESSAGE_FORMAT"].FormatExt(E.Origin.Name, E.Data);

            foreach (var server in E.Owner.Manager.GetServers())
            {
                server.Broadcast(message, E.Origin);
            }

            E.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Temporarily bans a client
    /// </summary>
    public class TempBanCommand : Command
    {
        private readonly ApplicationConfiguration _appConfig;
        
        public TempBanCommand(ApplicationConfiguration appConfig, CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "tempban";
            Description = _translationLookup["COMMANDS_TEMPBAN_DESC"];
            Alias = "tb";
            Permission = Permission.Administrator;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_DURATION"],
                    Required = true,
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_REASON"],
                    Required = true
                }
            };
            _appConfig = appConfig;
        }

        private static readonly string TempBanRegex = @"([0-9]+\w+)\ (.+)";

        public override async Task ExecuteAsync(GameEvent gameEvent)
        {
            var match = Regex.Match(gameEvent.Data, TempBanRegex);
            if (match.Success)
            {
                var tempbanReason = match.Groups[2].ToString().FindRuleForReason(_appConfig, gameEvent.Owner);
                var length = match.Groups[1].ToString().ParseTimespan();

                if (length > gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MaximumTempBanTime)
                {
                    gameEvent.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_FAIL_TOOLONG"]);
                }

                else
                {
                    switch ((await gameEvent.Target.TempBan(tempbanReason, length, gameEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken)).FailReason)
                    {
                        case GameEvent.EventFailReason.None:
                            gameEvent.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_SUCCESS"].FormatExt(gameEvent.Target, length.HumanizeForCurrentCulture()));
                            break;
                        case GameEvent.EventFailReason.Exception:
                            gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
                            break;
                        default:
                            gameEvent.Origin.Tell(_translationLookup["COMMANDS_TEMPBAN_FAIL"].FormatExt(gameEvent.Target.Name));
                            break;
                    }
                }
            }
        }
    }

    /// <summary>
    /// Permanently bans a client
    /// </summary>
    public class BanCommand : Command
    {
        private readonly ApplicationConfiguration _appConfig;
        public BanCommand(ApplicationConfiguration appConfig, CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "ban";
            Description = _translationLookup["COMMANDS_BAN_DESC"];
            Alias = "b";
            Permission = Permission.SeniorAdmin;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_REASON"],
                    Required = true
                }
            };
            _appConfig = appConfig;
        }

        public override async Task ExecuteAsync(GameEvent gameEvent)
        {
            var reason = gameEvent.Data.FindRuleForReason(_appConfig, gameEvent.Owner);
            switch ((await gameEvent.Target.Ban(reason, gameEvent.Origin, false).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken)).FailReason)
            {
                case GameEvent.EventFailReason.None:
                    gameEvent.Origin.Tell(_translationLookup["COMMANDS_BAN_SUCCESS"].FormatExt(gameEvent.Target.Name));
                    break;
                case GameEvent.EventFailReason.Exception:
                    gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
                    break;
                default:
                    gameEvent.Origin.Tell(_translationLookup["COMMANDS_BAN_FAIL"].FormatExt(gameEvent.Target.Name));
                    break;
            }
        }
    }

    /// <summary>
    /// Unbans a banned client
    /// </summary>
    public class UnbanCommand : Command
    {
        public UnbanCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "unban";
            Description = _translationLookup["COMMANDS_UNBAN_DESC"];
            Alias = "ub";
            Permission = Permission.SeniorAdmin;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_CLIENTID"],
                    Required = true,
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_REASON"],
                    Required  = true
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            // todo: don't do the lookup here
            var penalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId);
            if (penalties.Where(p => p.Type == EFPenalty.PenaltyType.Ban || p.Type == EFPenalty.PenaltyType.TempBan).FirstOrDefault() != null)
            {
                switch ((await E.Target.Unban(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
                {
                    case GameEvent.EventFailReason.None:
                        E.Origin.Tell(_translationLookup["COMMANDS_UNBAN_SUCCESS"].FormatExt(E.Target));
                        break;
                    default:
                        E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
                        break;
                }
            }

            else
            {
                E.Origin.Tell(_translationLookup["COMMANDS_UNBAN_FAIL"].FormatExt(E.Target));
            }
        }
    }

    /// <summary>
    /// Prints client information
    /// </summary>
    public class WhoAmICommand : Command
    {
        public WhoAmICommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "whoami";
            Description = _translationLookup["COMMANDS_WHO_DESC"];
            Alias = "who";
            Permission = Permission.User;
            RequiresTarget = false;
        }

        public override Task ExecuteAsync(GameEvent gameEvent)
        {
            var you = _translationLookup["COMMANDS_WHOAMI_FORMAT"].FormatExt(gameEvent.Origin.ClientNumber, gameEvent.Origin.ClientId, gameEvent.Origin.GuidString,
                gameEvent.Origin.IPAddressString, gameEvent.Origin.ClientPermission.Name, string.IsNullOrEmpty(gameEvent.Origin.Tag) ? "" : $" {gameEvent.Origin.Tag}^7", gameEvent.Origin.Name);
            gameEvent.Origin.Tell(you);

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// List online clients
    /// </summary>
    public class ListClientsCommand : Command
    {
        public ListClientsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "list";
            Description = _translationLookup["COMMANDS_LIST_DESC"];
            Alias = "l";
            Permission = Permission.Moderator;
            RequiresTarget = false;
        }

        public override Task ExecuteAsync(GameEvent gameEvent)
        {
            var clientList = gameEvent.Owner.GetClientsAsList()
                .Select(client => _translationLookup["COMMANDS_LIST_FORMAT"]
                .FormatExt(client.ClientPermission.Name, string.IsNullOrEmpty(client.Tag) ? "" : $" {client.Tag}^7", client.ClientNumber, client.Name))
                .ToArray();

            gameEvent.Origin.Tell(clientList);

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Prints help information
    /// </summary>
    public class HelpCommand : Command
    {
        public HelpCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "help";
            Description = _translationLookup["COMMANDS_HELP_DESC"];
            Alias = "h";
            Permission = Permission.User;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_COMMANDS"],
                    Required = false
                }
            };
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            string cmd = E.Data.Trim();

            if (cmd.Length > 2)
            {
                bool found = false;
                foreach (var command in E.Owner.Manager.GetCommands())
                {
                    if (command.Name == cmd.ToLower() ||
                        command.Alias == cmd.ToLower())
                    {
                        E.Origin.Tell($"[^3{command.Name}^7] {command.Description}");
                        E.Origin.Tell(command.Syntax);
                        found = true;
                    }
                }

                if (!found)
                {
                    E.Origin.Tell(_translationLookup["COMMANDS_HELP_NOTFOUND"]);
                }
            }

            else
            {
                int count = 0;
                StringBuilder helpResponse = new StringBuilder();
                var CommandList = E.Owner.Manager.GetCommands();

                foreach (Command C in CommandList)
                {
                    if (E.Origin.Level >= C.Permission)
                    {
                        helpResponse.Append(" [^3" + C.Name + "^7] ");
                        if (count >= 4)
                        {
                            E.Origin.Tell(helpResponse.ToString());
                            helpResponse = new StringBuilder();
                            count = 0;
                        }
                        count++;
                    }
                }
                E.Origin.Tell(helpResponse.ToString());
                E.Origin.Tell(_translationLookup["COMMANDS_HELP_MOREINFO"]);
            }

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Fast restarts the map
    /// </summary>
    public class FastRestartCommand : Command
    {
        public FastRestartCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "fastrestart";
            Description = _translationLookup["COMMANDS_FASTRESTART_DESC"];
            Alias = "fr";
            Permission = Permission.Moderator;
            RequiresTarget = false;
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            await E.Owner.ExecuteCommandAsync("fast_restart");

            var _ = !E.Origin.Masked ?
                  E.Owner.Broadcast($"^5{E.Origin.Name} ^7{_translationLookup["COMMANDS_FASTRESTART_UNMASKED"]}") :
                 E.Owner.Broadcast(_translationLookup["COMMANDS_FASTRESTART_MASKED"]);
        }
    }

    /// <summary>
    /// Cycles to the next map in rotation
    /// </summary>
    public class MapRotateCommand : Command
    {
        public MapRotateCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "maprotate";
            Description = _translationLookup["COMMANDS_MAPROTATE_DESC"];
            Alias = "mr";
            Permission = Permission.Administrator;
            RequiresTarget = false;
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            _ = !E.Origin.Masked ?
                E.Owner.Broadcast($"{_translationLookup["COMMANDS_MAPROTATE"]} [^5{E.Origin.Name}^7]", E.Origin) :
                E.Owner.Broadcast(_translationLookup["COMMANDS_MAPROTATE"], E.Origin);

            await Task.Delay(E.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds * 1000);
            await E.Owner.ExecuteCommandAsync("map_rotate");
        }
    }

    /// <summary>
    /// Sets the level of given client
    /// </summary>
    public class SetLevelCommand : Command
    {
        public SetLevelCommand(CommandConfiguration config, ITranslationLookup translationLookup, ILogger<SetLevelCommand> logger) : base(config, translationLookup)
        {
            Name = "setlevel";
            Description = _translationLookup["COMMANDS_SETLEVEL_DESC"];
            Alias = "sl";
            Permission = Permission.Moderator;
            RequiresTarget = true;
            Arguments = new[]
            {
                 new CommandArgument()
                 {
                     Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                     Required = true
                 },
                 new CommandArgument()
                 {
                     Name = _translationLookup["COMMANDS_ARGS_LEVEL"],
                     Required = true
                 }
            };
            this.logger = logger;
        }

        public override async Task ExecuteAsync(GameEvent gameEvent)
        {
            Permission oldPerm = gameEvent.Target.Level;
            Permission newPerm = Utilities.MatchPermission(gameEvent.Data);
            bool allowMultiOwner = gameEvent.Owner.Manager.GetApplicationSettings().Configuration().EnableMultipleOwners;
            bool steppedPrivileges = gameEvent.Owner.Manager.GetApplicationSettings().Configuration().EnableSteppedHierarchy;
            var targetClient = gameEvent.Target;

            // pre setup logic
            bool canPromoteSteppedPriv = gameEvent.Origin.Level > newPerm || gameEvent.Origin.Level == Permission.Owner;
            bool hasOwner = await gameEvent.Owner.Manager.GetClientService().GetOwnerCount() > 0;

            // trying to set self
            if (gameEvent.Target == gameEvent.Origin)
            {
                gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_SELF"]);
                return;
            }

            // origin permission not high enough
            else if (gameEvent.Origin.Level < gameEvent.Target.Level)
            {
                gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_PERMISSION"].FormatExt(gameEvent.Target.Name));
                return;
            }

            // trying to set owner without enabling multiple owners
            else if (newPerm == Permission.Owner && !allowMultiOwner && hasOwner)
            {
                // only one owner is allowed
                gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_OWNER"]);
                return;
            }

            // trying to set level when only owner is allowed to
            else if (gameEvent.Origin.Level < Permission.Owner && !steppedPrivileges)
            {
                // only the owner is allowed to set levels
                gameEvent.Origin.Tell($"{_translationLookup["COMMANDS_SETLEVEL_STEPPEDDISABLED"]} ^5{gameEvent.Target.Name}");
                return;
            }
            
            else if (gameEvent.Target.Level == Permission.Flagged)
            {
                gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_FLAGGED"].FormatExt(gameEvent.Target.Name));
                return;
            }

            // stepped privilege is enabled, but the new level is too high
            else if (steppedPrivileges && !canPromoteSteppedPriv)
            {
                // can't promote a client to higher than your current perms
                // or your peer
                gameEvent.Origin.Tell(string.Format(_translationLookup["COMMANDS_SETLEVEL_LEVELTOOHIGH"], gameEvent.Target.Name, (gameEvent.Origin.Level - 1).ToLocalizedLevelName()));
                return;
            }

            // valid
            else if (newPerm > Permission.Banned)
            {
                targetClient = targetClient.ClientNumber < 0 ?
                    gameEvent.Owner.Manager.GetActiveClients()
                    .FirstOrDefault(c => c.ClientId == targetClient?.ClientId) ?? targetClient : targetClient;

                logger.LogDebug("Beginning set level of client {origin} to {newPermission}", gameEvent.Origin.ToString(), newPerm);

                var result = await targetClient.SetLevel(newPerm, gameEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, gameEvent.Owner.Manager.CancellationToken);

                if (result.Failed)
                {
                    // user is the same level
                    if (result.FailReason == GameEvent.EventFailReason.Invalid)
                    {
                        gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_INVALID"]
                            .FormatExt(gameEvent.Target.Name, newPerm.ToString()));
                        return;
                    }
                    
                    using (LogContext.PushProperty("Server", gameEvent.Origin.CurrentServer?.ToString()))
                    {
                        logger.LogWarning("Failed to set level of client {origin} {reason}", 
                            gameEvent.Origin.ToString(), 
                            result.FailReason);
                    }
                    gameEvent.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
                    return;
                }

                // inform the client that they were promoted
                // we don't really want to tell them if they're demoted haha
                if (targetClient.IsIngame && newPerm > oldPerm)
                {
                    targetClient.Tell(_translationLookup["COMMANDS_SETLEVEL_SUCCESS_TARGET"].FormatExt(newPerm));
                }

                // inform the origin that the client has been updated
                _ = newPerm < oldPerm ?
                    gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_DEMOTE_SUCCESS"].FormatExt(targetClient.Name)) :
                    gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_SUCCESS"].FormatExt(targetClient.Name));
            }

            // all other tests failed so it's invalid group
            else
            {
                gameEvent.Origin.Tell(_translationLookup["COMMANDS_SETLEVEL_FAIL"]);
            }
        }
    }

    /// <summary>
    /// Prints the amount of memory IW4MAdmin is using
    /// </summary>
    public class MemoryUsageCommand : Command
    {
        public MemoryUsageCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "usage";
            Description = _translationLookup["COMMANDS_USAGE_DESC"];
            Alias = "us";
            Permission = Permission.Moderator;
            RequiresTarget = false;
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            E.Origin.Tell(_translationLookup["COMMANDS_USAGE_TEXT"].FormatExt(Math.Round(((System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 2048f) / 1200f), 1)));
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Prints out how long IW4MAdmin has been running
    /// </summary>
    public class UptimeCommand : Command
    {
        public UptimeCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "uptime";
            Description = _translationLookup["COMMANDS_UPTIME_DESC"];
            Alias = "up";
            Permission = Permission.Moderator;
            RequiresTarget = false;
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            var uptime = DateTime.Now - System.Diagnostics.Process.GetCurrentProcess().StartTime;
            var loc = _translationLookup;
            E.Origin.Tell(loc["COMMANDS_UPTIME_TEXT"].FormatExt(uptime.HumanizeForCurrentCulture(4)));
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Lists all unmasked admins
    /// </summary>
    public class ListAdminsCommand : Command
    {
        public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "admins";
            Description = _translationLookup["COMMANDS_ADMINS_DESC"];
            Alias = "a";
            Permission = Permission.User;
            RequiresTarget = false;
        }

        public static string OnlineAdmins(Server S, ITranslationLookup lookup)
        {
            var onlineAdmins = S.GetClientsAsList()
                .Where(p => p.Level > Permission.Flagged)
                .Where(p => !p.Masked)
                .Select(p => $"[^3{Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}^7] {p.Name}");

            return onlineAdmins.Count() > 0 ?
                string.Join(Environment.NewLine, onlineAdmins) :
                lookup["COMMANDS_ADMINS_NONE"];
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            foreach (string line in OnlineAdmins(E.Owner, _translationLookup).Split(Environment.NewLine))
            {
                var _ = E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix) ? E.Owner.Broadcast(line) : E.Origin.Tell(line);
            }

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Attempts to load the specified map
    /// </summary>
    public class LoadMapCommand : Command
    {
        public LoadMapCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "map";
            Description = _translationLookup["COMMANDS_MAP_DESC"];
            Alias = "m";
            Permission = Permission.Administrator;
            RequiresTarget = false;
            Arguments = new[]
            {
                 new CommandArgument()
                 {
                     Name = _translationLookup["COMMANDS_ARGS_MAP"],
                     Required = true
                 }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            string newMap = E.Data.Trim();
            int delay = E.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds * 1000;

            var foundMap = E.Owner.Maps.FirstOrDefault(_map => _map.Name.Equals(newMap, StringComparison.InvariantCultureIgnoreCase) ||
            _map.Alias.Equals(newMap, StringComparison.InvariantCultureIgnoreCase));

            _ = foundMap == null ?
                E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_UKN"].FormatExt(newMap)) :
                 E.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(foundMap.Alias));

            await Task.Delay(delay);
            await E.Owner.LoadMap(foundMap?.Name ?? newMap);
        }
    }

    /// <summary>
    /// Finds player by name
    /// </summary>
    public class FindPlayerCommand : Command
    {
        public FindPlayerCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "find";
            Description = _translationLookup["COMMANDS_FIND_DESC"];
            Alias = "f";
            Permission = Permission.Administrator;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            if (E.Data.Length < 3)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_FIND_MIN"]);
                return;
            }

            var db_players = (await (E.Owner.Manager.GetClientService() as ClientService).FindClientsByIdentifier(E.Data));

            if (db_players.Count == 0)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_FIND_EMPTY"]);
                return;
            }

            foreach (var client in db_players)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_FIND_FORMAT"].FormatExt(client.Name, client.ClientId, Utilities.ConvertLevelToColor((Permission)client.LevelInt, client.Level), client.IPAddress, client.LastConnectionText));
            }
        }
    }

    /// <summary>
    /// Lists server and global rules
    /// </summary>
    public class ListRulesCommands : Command
    {
        public ListRulesCommands(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "rules";
            Description = _translationLookup["COMMANDS_RULES_DESC"];
            Alias = "r";
            Permission = Permission.User;
            RequiresTarget = false;
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            if (E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules?.Length < 1 &&
                E.Owner.ServerConfig.Rules?.Length < 1)
            {
                var _ = E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix) ?
                      E.Owner.Broadcast(_translationLookup["COMMANDS_RULES_NONE"]) :
                      E.Origin.Tell(_translationLookup["COMMANDS_RULES_NONE"]);
            }

            else
            {
                var rules = new List<string>();
                rules.AddRange(E.Owner.Manager.GetApplicationSettings().Configuration().GlobalRules);
                if (E.Owner.ServerConfig.Rules != null)
                {
                    rules.AddRange(E.Owner.ServerConfig.Rules);
                }

                var ruleFomat = rules.Select(r => $"- {r}");
                if (E.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix))
                {
                    E.Owner.Broadcast(ruleFomat);
                }
                else
                {
                    E.Origin.Tell(ruleFomat);
                }
            }

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Sends a private message to another player
    /// </summary>
    public class PrivateMessageCommand : Command
    {
        public PrivateMessageCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "privatemessage";
            Description = _translationLookup["COMMANDS_PM_DESC"];
            Alias = "pm";
            Permission = Permission.User;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
                    Required = true
                }
            };
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            E.Target.Tell($"^1{E.Origin.Name} ^3[PM]^7 - {E.Data}");
            E.Origin.Tell($"To ^3{E.Target.Name} ^7-> {E.Data}");
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Flag given client for specified reason
    /// </summary>
    public class FlagClientCommand : Command
    {
        public FlagClientCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "flag";
            Description = _translationLookup["COMMANDS_FLAG_DESC"];
            Alias = "fp";
            Permission = Permission.Moderator;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_REASON"],
                    Required = true
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            switch ((await E.Target.Flag(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
            {
                case GameEvent.EventFailReason.Permission:
                    E.Origin.Tell(_translationLookup["COMMANDS_FLAG_FAIL"].FormatExt(E.Target.Name));
                    break;
                case GameEvent.EventFailReason.Invalid:
                    E.Origin.Tell($"{_translationLookup["COMMANDS_FLAG_ALREADYFLAGGED"]}");
                    break;
                case GameEvent.EventFailReason.None:
                    E.Origin.Tell(_translationLookup["COMMANDS_FLAG_SUCCESS"].FormatExt(E.Target.Name));
                    break;
                default:
                    E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
                    break;
            }
        }
    }

    /// <summary>
    /// Unflag given client for specified reason
    /// </summary>
    public class UnflagClientCommand : Command
    {
        public UnflagClientCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "unflag";
            Description = _translationLookup["COMMANDS_UNFLAG_DESC"];
            Alias = "uf";
            Permission = Permission.Moderator;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_REASON"],
                    Required = true
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            switch ((await E.Target.Unflag(E.Data, E.Origin).WaitAsync(Utilities.DefaultCommandTimeout, E.Owner.Manager.CancellationToken)).FailReason)
            {
                case GameEvent.EventFailReason.None:
                    E.Origin.Tell(_translationLookup["COMMANDS_FLAG_UNFLAG"].FormatExt(E.Target.Name));
                    break;
                case GameEvent.EventFailReason.Permission:
                    E.Origin.Tell(_translationLookup["COMMANDS_UNFLAG_FAIL"].FormatExt(E.Target.Name));
                    break;
                case GameEvent.EventFailReason.Invalid:
                    E.Origin.Tell(_translationLookup["COMMANDS_UNFLAG_NOTFLAGGED"]);
                    break;
                default:
                    E.Origin.Tell(_translationLookup["SERVER_ERROR_COMMAND_INGAME"]);
                    break;
            }
        }
    }

    /// <summary>
    /// Report client for given reason
    /// </summary>
    public class ReportClientCommand : Command
    {
        public ReportClientCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "report";
            Description = _translationLookup["COMMANDS_REPORT_DESC"];
            Alias = "rep";
            Permission = Permission.User;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                },
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_REASON"],
                    Required = true
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent commandEvent)
        {
            if (commandEvent.Data.ToLower().Contains("camp"))
            {
                commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_CAMP"]);
                return;
            }

            bool success = false;

            switch ((await commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin).WaitAsync(Utilities.DefaultCommandTimeout, commandEvent.Owner.Manager.CancellationToken)).FailReason)
            {
                case GameEvent.EventFailReason.None:
                    commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_SUCCESS"]);
                    success = true;
                    break;
                case GameEvent.EventFailReason.Exception:
                    commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_DUPLICATE"]);
                    break;
                case GameEvent.EventFailReason.Permission:
                    commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL"].FormatExt(commandEvent.Target.Name));
                    break;
                case GameEvent.EventFailReason.Invalid:
                    commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_SELF"]);
                    break;
                case GameEvent.EventFailReason.Throttle:
                    commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_TOOMANY"]);
                    break;
            }

            if (success)
            {
                commandEvent.Owner.ToAdmins(String.Format("^5{0}^7->^1{1}^7: {2}", commandEvent.Origin.Name, commandEvent.Target.Name, commandEvent.Data));
            }
        }
    }

    /// <summary>
    /// List all reports on the server
    /// </summary>
    public class ListReportsCommand : Command
    {
        public ListReportsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "reports";
            Description = _translationLookup["COMMANDS_REPORTS_DESC"];
            Alias = "reps";
            Permission = Permission.Moderator;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_CLEAR"],
                    Required = false
                }
            };
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            if (E.Data != null && E.Data.ToLower().Contains(_translationLookup["COMMANDS_ARGS_CLEAR"]))
            {
                E.Owner.Reports = new List<Report>();
                E.Origin.Tell(_translationLookup["COMMANDS_REPORTS_CLEAR_SUCCESS"]);
                return Task.CompletedTask;
            }

            if (E.Owner.Reports.Count < 1)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_REPORTS_NONE"]);
                return Task.CompletedTask;
            }

            foreach (Report R in E.Owner.Reports)
            {
                E.Origin.Tell(String.Format("^5{0}^7->^1{1}^7: {2}", R.Origin.Name, R.Target.Name, R.Reason));
            }

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Masks client from announcements and online admin list
    /// </summary>
    public class MaskCommand : Command
    {
        public MaskCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "mask";
            Description = _translationLookup["COMMANDS_MASK_DESC"];
            Alias = "hide";
            Permission = EFClient.Permission.Moderator;
            RequiresTarget = false;
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            if (E.Origin.Masked)
            {
                E.Origin.Masked = false;
                E.Origin.Tell(_translationLookup["COMMANDS_MASK_OFF"]);
            }
            else
            {
                E.Origin.Masked = true;
                E.Origin.Tell(_translationLookup["COMMANDS_MASK_ON"]);
            }

            await E.Owner.Manager.GetClientService().Update(E.Origin);
        }
    }

    /// <summary>
    /// Lists ban information for given client
    /// </summary>
    public class ListBanInfoCommand : Command
    {
        public ListBanInfoCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "baninfo";
            Description = _translationLookup["COMMANDS_BANINFO_DESC"];
            Alias = "bi";
            Permission = Permission.Moderator;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            var existingPenalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.IPAddress);
            var penalty = existingPenalties.FirstOrDefault(b => b.Type > EFPenalty.PenaltyType.Kick);

            if (penalty == null)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_BANINFO_NONE"]);
                return;
            }

            if (penalty.Type == EFPenalty.PenaltyType.Ban)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_BANINFO_SUCCESS"].FormatExt(E.Target.Name, penalty.Offense));
            }

            else
            {
                string remainingTime = (penalty.Expires.Value - DateTime.UtcNow).HumanizeForCurrentCulture();
                E.Origin.Tell(_translationLookup["COMMANDS_BANINFO_TB_SUCCESS"].FormatExt(E.Target.Name, penalty.Offense, remainingTime));
            }
        }
    }

    /// <summary>
    /// Lists alises of specified client
    /// </summary>
    public class ListAliasesCommand : Command
    {
        public ListAliasesCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "alias";
            Description = _translationLookup["COMMANDS_ALIAS_DESC"];
            Alias = "known";
            Permission = EFClient.Permission.Moderator;
            RequiresTarget = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = true,
                }
            };
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            StringBuilder message = new StringBuilder();
            var names = new List<string>(E.Target.AliasLink.Children.Select(a => a.Name));
            var IPs = new List<string>(E.Target.AliasLink.Children.Select(a => a.IPAddress.ConvertIPtoString()).Distinct());

            E.Origin.Tell($"[^3{E.Target}^7]");

            message.Append($"{_translationLookup["COMMANDS_ALIAS_ALIASES"]}: ");
            message.Append(String.Join(" | ", names));
            E.Origin.Tell(message.ToString());

            message.Clear();
            message.Append($"{_translationLookup["COMMANDS_ALIAS_IPS"]}: ");
            message.Append(String.Join(" | ", IPs));
            E.Origin.Tell(message.ToString());

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Executes RCon command
    /// </summary>
    public class ExecuteRConCommand : Command
    {
        public ExecuteRConCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "rcon";
            Description = _translationLookup["COMMANDS_RCON_DESC"];
            Alias = "rcon";
            Permission = Permission.Owner;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_COMMANDS"],
                    Required = true
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            var Response = await E.Owner.ExecuteCommandAsync(E.Data.Trim());
            foreach (string S in Response)
            {
                E.Origin.Tell(S);
            }

            if (Response.Length == 0)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_RCON_SUCCESS"]);
            }
        }
    }

    /// <summary>
    /// Lists the loaded plugins
    /// </summary>
    /*public class ListPluginsCommand : Command
    {
        private readonly IPluginImporter _pluginImporter;
        public ListPluginsCommand(CommandConfiguration config, ITranslationLookup translationLookup, IPluginImporter pluginImporter) : base(config, translationLookup)
        {
            Name = "plugins";
            Description = _translationLookup["COMMANDS_PLUGINS_DESC"];
            Alias = "p";
            Permission = Permission.Administrator;
            RequiresTarget = false;
            _pluginImporter = pluginImporter;
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            E.Origin.Tell(_translationLookup["COMMANDS_PLUGINS_LOADED"]);
            foreach (var P in _pluginImporter.ActivePlugins)
            {
                E.Origin.Tell(string.Format("^3{0} ^7[v^3{1}^7] by ^5{2}^7", P.Name, P.Version, P.Author));
            }
            return Task.CompletedTask;
        }
    }*/

    /// <summary>
    /// Lists external IP
    /// </summary>
    public class ListExternalIPCommand : Command
    {
        public ListExternalIPCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "getexternalip";
            Description = _translationLookup["COMMANDS_IP_DESC"];
            Alias = "ip";
            Permission = Permission.User;
            RequiresTarget = false;
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            E.Origin.Tell(_translationLookup["COMMANDS_IP_SUCCESS"].FormatExt(E.Origin.IPAddressString));
            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Prunes inactive privileged clients
    /// </summary>
    public class PruneAdminsCommand : Command
    {
        private readonly IDatabaseContextFactory _contextFactory;
        
        public PruneAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup, 
            IDatabaseContextFactory contextFactory) : base(config, translationLookup)
        {
            Name = "prune";
            Description = _translationLookup["COMMANDS_PRUNE_DESC"];
            Alias = "pa";
            Permission = Permission.Owner;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_INACTIVE"],
                    Required = false
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            int inactiveDays = 30;

            try
            {
                if (E.Data.Length > 0)
                {
                    inactiveDays = int.Parse(E.Data);
                    if (inactiveDays < 1)
                    {
                        throw new FormatException();
                    }
                }
            }

            catch (FormatException)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_PRUNE_FAIL"]);
                return;
            }

            List<EFClient> inactiveUsers = null;
            // todo: make an event for this
            // update user roles
            await using var context = _contextFactory.CreateContext();
            var lastActive = DateTime.UtcNow.AddDays(-inactiveDays);
            inactiveUsers = await context.Clients
                .Where(c => c.Level > Permission.Flagged && c.Level <= Permission.Moderator)
                .Where(c => c.LastConnection < lastActive)
                .Select(c => c.ToPartialClient())
                .ToListAsync();
            inactiveUsers.ForEach(c => c.SetLevel(Permission.User, E.Origin));
            await context.SaveChangesAsync();
      
            E.Origin.Tell(_translationLookup["COMMANDS_PRUNE_SUCCESS"].FormatExt(inactiveUsers.Count));
        }
    }


    /// <summary>
    /// Sets login password
    /// </summary>
    public class SetPasswordCommand : Command
    {
        public SetPasswordCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "setpassword";
            Description = _translationLookup["COMMANDS_SETPASSWORD_DESC"];
            Alias = "sp";
            Permission = Permission.Moderator;
            RequiresTarget = false;
            AllowImpersonation = true;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PASSWORD"],
                    Required = true
                }
            };
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            if (E.Data.Length < 5)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_PASSWORD_FAIL"]);
                return;
            }

            string[] hashedPassword = Helpers.Hashing.Hash(E.Data);

            E.Origin.Password = hashedPassword[0];
            E.Origin.PasswordSalt = hashedPassword[1];

            await E.Owner.Manager.GetClientService().Update(E.Origin);
            E.Origin.Tell(_translationLookup["COMMANDS_PASSWORD_SUCCESS"]);
        }
    }

    /// <summary>
    /// Gets the ping of a client
    /// </summary>
    public class GetClientPingCommand : Command
    {
        public GetClientPingCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "ping";
            Description = _translationLookup["COMMANDS_PING_DESC"];
            Alias = "pi";
            Permission = Permission.User;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
                    Required = false
                }
            };
        }

        public override Task ExecuteAsync(GameEvent E)
        {
            if (E.Target == null)
            {
                E.Target = E.Owner.GetClientByName(E.Data).FirstOrDefault();
            }

            if (E.Target == null)
            {
                E.Origin.Tell(_translationLookup["COMMANDS_PING_SELF"].FormatExt(E.Origin.Ping));
            }
            else
            {
                E.Origin.Tell(_translationLookup["COMMANDS_PING_TARGET"].FormatExt(E.Target.Name, E.Target.Ping));
            }

            return Task.CompletedTask;
        }
    }

    /// <summary>
    /// Sets the email for gravatar in webfront
    /// </summary>
    public class SetGravatarCommand : Command
    {
        private readonly IMetaService _metaService;

        public SetGravatarCommand(CommandConfiguration config, ITranslationLookup translationLookup, IMetaService metaService) : base(config, translationLookup)
        {
            Name = "setgravatar";
            Description = _translationLookup["COMMANDS_GRAVATAR_DESC"];
            Alias = "sg";
            Permission = Permission.User;
            RequiresTarget = false;
            Arguments = new[]
            {
                new CommandArgument()
                {
                    Name = _translationLookup["COMMANDS_ARGS_GRAVATAR"],
                    Required = true
                }
            };

            _metaService = metaService;
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            using (var md5 = MD5.Create())
            {
                string gravatarEmail = string.Concat(md5.ComputeHash(E.Data.ToLower().Select(d => Convert.ToByte(d)).ToArray())
                                .Select(h => h.ToString("x2")));
                await _metaService.AddPersistentMeta("GravatarEmail", gravatarEmail, E.Origin);
            }

            E.Origin.Tell(_translationLookup["COMMANDS_GRAVATAR_SUCCESS_NEW"]);
        }
    }

    /// <summary>
    /// Retrieves the next map in rotation
    /// </summary>
    public class NextMapCommand : Command
    {
        public NextMapCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
        {
            Name = "nextmap";
            Description = _translationLookup["COMMANDS_NEXTMAP_DESC"];
            Alias = "nm";
            Permission = EFClient.Permission.User;
            RequiresTarget = false;
        }

        public static async Task<string> GetNextMap(Server s, ITranslationLookup lookup)
        {
            string mapRotation = (await s.GetDvarAsync<string>("sv_mapRotation")).Value?.ToLower() ?? "";
            var regexMatches = Regex.Matches(mapRotation, @"((?:gametype|exec) +(?:([a-z]{1,4})(?:.cfg)?))? *map ([a-z|_|\d]+)", RegexOptions.IgnoreCase).ToList();

            // find the current map in the rotation
            var currentMap = regexMatches.Where(m => m.Groups[3].ToString() == s.CurrentMap.Name);
            var lastMap = regexMatches.LastOrDefault();
            Map nextMap = null;

            // no maprotation at all
            if (regexMatches.Count() == 0)
            {
                return lookup["COMMANDS_NEXTMAP_SUCCESS"].FormatExt(s.CurrentMap.Alias, Utilities.GetLocalizedGametype(s.Gametype));
            }

            // the current map is not in rotation
            if (currentMap.Count() == 0)
            {
                return lookup["COMMANDS_NEXTMAP_NOT_IN_ROTATION"];
            }

            // there's duplicate maps in rotation
            else if (currentMap.Count() > 1)
            {
                // gametype has been manually specified
                var duplicateMaps = currentMap.Where(m => !string.IsNullOrEmpty(m.Groups[1].ToString()));

                // more than one instance of map in rotation
                if (duplicateMaps.Count() > 0)
                {
                    currentMap = duplicateMaps.Where(m => m.Groups[2].ToString() == s.Gametype);
                }

                // else we just have to assume it's the first one
            }

            // if the current map is the last map, the next map is the first map
            var nextMapMatch = currentMap.First().Index != lastMap.Index ?
               regexMatches[regexMatches.IndexOf(currentMap.First()) + 1] :
               regexMatches.First();

            string nextMapName = nextMapMatch.Groups[3].ToString();

            nextMap = s.Maps.FirstOrDefault(m => m.Name == nextMapMatch.Groups[3].ToString()) ?? new Map() { Alias = nextMapName, Name = nextMapName };
            string nextGametype = nextMapMatch.Groups[2].ToString().Length == 0 ?
                Utilities.GetLocalizedGametype(s.Gametype) :
                Utilities.GetLocalizedGametype(nextMapMatch.Groups[2].ToString());

            return lookup["COMMANDS_NEXTMAP_SUCCESS"].FormatExt(nextMap.Alias, nextGametype);
        }

        public override async Task ExecuteAsync(GameEvent E)
        {
            E.Origin.Tell(await GetNextMap(E.Owner, _translationLookup));
        }
    }
}