/***********************************************************************************************************************/
/*** DO NOT edit this file! Edit the files under `oxide/config` and/or `oxide/lang`, created once plugin has loaded. ***/
/*** Please note, support cannot be provided if the plugin has been modified. Please use a fresh copy if modified.   ***/
/***********************************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using Oxide.Core.Libraries.Covalence;

namespace Oxide.Plugins
{
    [Info("Command Block", "Wulf", "0.5.3")]
    [Description("Blocks configured commands from being executed on the server")]
    public class CommandBlock : CovalencePlugin
    {
        #region Configuration

        private Configuration config;

        private class Configuration
        {
            [JsonProperty("Block commands sent by players")]
            public bool BlockPlayers = true;

            /*[JsonProperty("Block commands sent by RCON")]
            public bool BlockRcon = true;*/

            [JsonProperty("Block commands sent by server")]
            public bool BlockServer = false;

            [JsonProperty("List of commands to block", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public List<string> BlockedCommands = new List<string> { "spectate", "kill", "status" };

            [JsonProperty("Blocked commands (full or short commands)")] // TODO: From version 0.3.1; remove eventually
            private List<string> BlockedCommandsOld
            { set { BlockedCommands = value; } }

            [JsonProperty("Log blocked command attempts")]
            public bool LogAttempts = false;

            [JsonProperty("Log blocked command attempts (true/false)")] // TODO: From version 0.3.1; remove eventually
            private bool LogAttemptsOld
            { set { LogAttempts = value; } }

            public string ToJson() => JsonConvert.SerializeObject(this);

            public Dictionary<string, object> ToDictionary() => JsonConvert.DeserializeObject<Dictionary<string, object>>(ToJson());
        }

        protected override void LoadDefaultConfig() => config = new Configuration();

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null)
                {
                    throw new JsonException();
                }

                if (!config.ToDictionary().Keys.SequenceEqual(Config.ToDictionary(x => x.Key, x => x.Value).Keys))
                {
                    LogWarning("Configuration appears to be outdated; updating and saving");
                    SaveConfig();
                }
            }
            catch
            {
                LogWarning($"Configuration file {Name}.json is invalid; using defaults");
                LoadDefaultConfig();
            }
        }

        protected override void SaveConfig()
        {
            LogWarning($"Configuration changes saved to {Name}.json");
            Config.WriteObject(config, true);
        }

        #endregion Configuration

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["BlockCommand"] = "blockcommand",
                ["BlockCommandAdd"] = "add",
                ["BlockCommandList"] = "list",
                ["BlockCommandRemove"] = "remove",
                ["BlockCommandAddUsage"] = "Usage: {0} add <command>",
                ["BlockCommandListUsage"] = "Usage: {0} list",
                ["BlockCommandRemoveUsage"] = "Usage: {0} remove <command>",
                ["CommandAdded"] = "Command '{0}' was added to the command block list",
                ["CommandAttempted"] = "{0} ({1}) attempted to use blocked command: {2}",
                ["CommandBlocked"] = "Sorry, the '{0}' command is blocked",
                ["CommandListed"] = "Command '{0}' is already in the command block list",
                ["CommandNotListed"] = "Command '{0}' is not in the command block list",
                ["CommandRemoved"] = "Command '{0}' was removed from the command block list",
                ["CommandsBlocked"] = "Blocked commands: {0}",
                ["NotAllowed"] = "You are not allowed to use the '{0}' command"
            }, this);
        }

        #endregion Localization

        #region Initialization

        private const string permAdmin = "commandblock.admin";
        private const string permBypass = "commandblock.bypass";

        private void Init()
        {
            AddLocalizedCommand(nameof(BlockCommand));

            permission.RegisterPermission(permAdmin, this);
            permission.RegisterPermission(permBypass, this);

            if (!config.BlockPlayers)
            {
                Unsubscribe(nameof(OnUserCommand));
            }
#if RUST
            if (!config.BlockServer && !config.BlockPlayers)
#else
            if (!config.BlockServer)
#endif
            {
                Unsubscribe(nameof(OnServerCommand));
            }
        }

        #endregion Initialization

        #region Commands

        private bool IsValidSubCommand(string subCommand, IPlayer player)
        {
            return subCommand.Equals(GetLang("BlockCommandAdd", player.Id), StringComparison.OrdinalIgnoreCase)
                || subCommand.Equals(GetLang("BlockCommandRemove", player.Id), StringComparison.OrdinalIgnoreCase)
                || subCommand.Equals(GetLang("BlockCommandList", player.Id), StringComparison.OrdinalIgnoreCase);
        }

        private void BlockCommand(IPlayer player, string command, string[] args)
        {
            if (!player.HasPermission(permAdmin))
            {
                Message(player, "NotAllowed", command);
                return;
            }

            if (args.Length < 1 || !IsValidSubCommand(args[0], player))
            {
                ShowHelp(player, command);
                return;
            }

            string commandArg = string.Join(" ", args.Skip(1).Select(x => x.TrimStart('/')).ToArray());

            if (args.Length > 1 && args[0].Equals(GetLang("BlockCommandAdd", player.Id), StringComparison.OrdinalIgnoreCase))
            {
                if (config.BlockedCommands.Contains(commandArg))
                {
                    Message(player, "CommandListed", commandArg);
                    return;
                }

                config.BlockedCommands.Add(commandArg);
                SaveConfig();

                Message(player, "CommandAdded", commandArg);
            }
            else if (args.Length > 1 && args[0].Equals(GetLang("BlockCommandRemove", player.Id), StringComparison.OrdinalIgnoreCase))
            {
                if (!config.BlockedCommands.Contains(commandArg))
                {
                    Message(player, "CommandNotListed", commandArg);
                    return;
                }

                config.BlockedCommands.Remove(commandArg);
                SaveConfig();

                Message(player, "CommandRemoved", commandArg);
            }
            else if (args[0].Equals(GetLang("BlockCommandList", player.Id), StringComparison.OrdinalIgnoreCase))
            {
                Message(player, "CommandsBlocked", string.Join(", ", config.BlockedCommands.ToArray()));
            }
            else
            {
                ShowHelp(player, command, args[0]);
            }
        }

        #endregion Commands

        #region Command Blocking

        private bool IsCommandBlocked(string command, string[] args)
        {
            if (config.BlockedCommands.Contains(command, StringComparer.OrdinalIgnoreCase)
                || config.BlockedCommands.Contains(string.Concat(command, args), StringComparer.OrdinalIgnoreCase))
            {
                return true;
            }

            string splitCommand = command.Substring(command.IndexOf(".", StringComparison.Ordinal) + 1);
            if (config.BlockedCommands.Contains(splitCommand, StringComparer.OrdinalIgnoreCase)
                || config.BlockedCommands.Contains(string.Concat(splitCommand, args), StringComparer.OrdinalIgnoreCase))
            {
                return true;
            }

            return false;
        }

        // TODO: Add support for OnRconCommand once IRemoteClient is added
        /*private object OnRconCommand(IRemoteClient client, string command, string[] args)
        {
            return null;
        }*/

#if RUST

        private object OnServerCommand(ConsoleSystem.Arg arg)
        {
            IPlayer player = (arg.Connection?.player as BasePlayer)?.IPlayer;
            if (IsCommandBlocked(arg.cmd.FullName, Oxide.Game.Rust.Libraries.Covalence.RustCommandSystem.ExtractArgs(arg)))
            {
                if (config.BlockPlayers && player != null && !player.HasPermission(permBypass))
                {
                    if (config.LogAttempts)
                    {
                        LogWarning(GetLang("CommandAttempted", null, player.Name, player.Id, arg.cmd.FullName));
                    }

                    Message(player, "CommandBlocked", arg.cmd.FullName);
                    return true;
                }
                else if (config.BlockServer)
                {
                    if (config.LogAttempts)
                    {
                        LogWarning(GetLang("CommandAttempted", null, arg.cmd.FullName));
                    }

                    Log(GetLang("CommandBlocked", null, arg.cmd.FullName));
                    return true;
                }
            }

            return null;
        }

#else
        private object OnServerCommand(string command, string[] args)
        {
            if (IsCommandBlocked(command, args))
            {
                if (config.LogAttempts)
                {
                    LogWarning(GetLang("CommandAttempted", null, command));
                }

                Log(GetLang("CommandBlocked", null, command));
                return true;
            }

            return null;
        }
#endif

        private object OnUserCommand(IPlayer player, string command, string[] args)
        {
            if (IsCommandBlocked(command, args) && !player.HasPermission(permBypass))
            {
                if (config.LogAttempts)
                {
                    LogWarning(GetLang("CommandAttempted", null, player.Name, player.Id, command));
                }

                Message(player, "CommandBlocked", command);
                return true;
            }

            return null;
        }

        #endregion Command Blocking

        #region Helpers

        private void ShowHelp(IPlayer player, string command, string subCommand = null)
        {
            if (player.LastCommand.Equals(CommandType.Chat))
            {
                command = "/" + command; // TODO: Switch to prefix option when available
            }

            if (string.IsNullOrEmpty(subCommand))
            {
                MessageBlock(player, new Dictionary<string, string[]>
                {
                    ["BlockCommandAddUsage"] = new[] { command },
                    ["BlockCommandRemoveUsage"] = new[] { command },
                    ["BlockCommandListUsage"] = new[] { command },
                });
                return;
            }

            if (subCommand.Equals(GetLang("BlockCommandAdd", player.Id), StringComparison.OrdinalIgnoreCase))
            {
                Message(player, "BlockCommandAddUsage", command);
            }
            else if (subCommand.Equals(GetLang("BlockCommandRemove", player.Id), StringComparison.OrdinalIgnoreCase))
            {
                Message(player, "BlockCommandRemoveUsage", command);
            }
            else if (subCommand.Equals(GetLang("BlockCommandList", player.Id), StringComparison.OrdinalIgnoreCase))
            {
                Message(player, "BlockCommandListUsage", command);
            }
        }

        private void SendHelpText(object obj)
        {
            IPlayer player = players.FindPlayerByObj(obj);
            if (player != null && player.HasPermission(permAdmin))
            {
                ShowHelp(player, GetLang(nameof(BlockCommand), player.Id));
            }
        }

        private void AddLocalizedCommand(string command)
        {
            foreach (string language in lang.GetLanguages(this))
            {
                Dictionary<string, string> messages = lang.GetMessages(language, this);
                foreach (KeyValuePair<string, string> message in messages)
                {
                    if (message.Key.Equals(command))
                    {
                        if (!string.IsNullOrEmpty(message.Value))
                        {
                            AddCovalenceCommand(message.Value, command);
                        }
                    }
                }
            }
        }

        private string GetLang(string langKey, string playerId = null, params object[] args)
        {
            return string.Format(lang.GetMessage(langKey, this, playerId), args);
        }

        private void Message(IPlayer player, string textOrLang, params object[] args)
        {
            if (player.IsConnected)
            {
                string message = GetLang(textOrLang, player.Id, args);
                player.Reply(message != textOrLang ? message : textOrLang);
            }
        }

        private void MessageBlock(IPlayer player, Dictionary<string, string[]> textsOrLang)
        {
            if (player.IsConnected)
            {
                StringBuilder combinedText = new StringBuilder();
                foreach (KeyValuePair<string, string[]> textOrLang in textsOrLang)
                {
                    string message = GetLang(textOrLang.Key, player.Id, textOrLang.Value);
                    combinedText.AppendLine(message != textOrLang.Key ? message : string.Format(textOrLang.Key, textOrLang.Value));
                }
                if (combinedText.Length > 0)
                {
                    player.Reply(combinedText.ToString());
                }
            }
        }

        #endregion Helpers
    }
}
