using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;

namespace Oxide.Plugins
{
    [Info("Tell Me X" , "Krungh Crow" , "2.0.0")]
    [Description("Calculate X and get rewards")]
    public class TellMeX : RustPlugin
    {
        #region Plugin References
        [PluginReference]
        Plugin Battlepass, ServerRewards, Economics, GUIAnnouncements;
        #endregion

        #region Variables & Localization
        private const string AdminPermission = "tellmex.admin";

        private Configuration _config;

        private bool _gameIsActive = false;
        private float _gameEndTime;
        private float _nextGameTime;

        private List<ulong> _playersWhoAnswered;
        private string _mathExpression = string.Empty;
        private string _correctAnswer = string.Empty;
        private Reward _selectedReward;
        private int _selectedQuantity = 0;

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string , string>
            {
                ["StartTellMeXMsg"] = "X = <color=yellow>{0}</color>" ,
                ["CommandTellMeXMsg"] = "CALCULATE X AND TELL IT TO ME! (example: /x 1234)" ,
                ["NextTellMeXMsg"] = "Next 'Tell Me X' will start in {0} seconds" ,
                ["AlreadyTellMeXMsg"] = "You've already played! Try again in {0} seconds" ,
                ["InvalidTellMeXMsg"] = "Invalid guess. Try something like /x 1234" ,
                ["NotNumericTellMeXMsg"] = "It is not a number! Try something like /x 1234" ,
                ["WonTellMeXMsg"] = "{0} did find X and has won: {1}" ,
                ["EndTellMeXMsg"] = "X was {0}" ,
                ["ExpiredTellMeXMsg"] = "was not found in time!" ,
                ["LoseTellMeXMsg"] = "X is not equal to this. You earned a consolation prize: {0}" ,
                ["SorryErrorMsg"] = "Sorry, an error has occurred! Please tell Krungh Crow about this. The item to give was 'null'." ,
                ["AdminStartEvent"] = "You have manually started the 'Tell Me X' event!" ,
                ["NoPermission"] = "You do not have permission to use this command." ,
                ["TimerDisplay"] = "Next 'Tell Me X' starts in: "
            } , this , "en");
        }
        #endregion

        #region Configuration
        // Represents a single reward item with its properties
        public class Reward
        {
            public string ItemShortname { get; set; }
            public ulong SkinId { get; set; }
            public string CustomName { get; set; }
            public int MinQuantity { get; set; }
            public int MaxQuantity { get; set; }
        }

        private class Configuration
        {
            public string Prefix { get; set; } = "[TellMeX] ";
            public ulong SteamIDIcon { get; set; } = 76561198842176097;
            public bool UseGUIAnnouncement { get; set; } = false;

            public bool UseServerRewards { get; set; } = false;
            public bool UseEconomics { get; set; } = false;
            public int PointsOnWin { get; set; } = 250;
            public int PointsOnLoss { get; set; } = 25;
            public bool UseBattlepass { get; set; } = false;
            public bool UseBattlepassFirstCurrency { get; set; } = false;
            public bool UseBattlepassSecondCurrency { get; set; } = false;
            public bool UseBattlepassOnLoss { get; set; } = false;
            public int BattlepassWinReward1 { get; set; } = 20;
            public int BattlepassWinReward2 { get; set; } = 20;
            public int BattlepassLossReward1 { get; set; } = 10;
            public int BattlepassLossReward2 { get; set; } = 10;

            public float GameIntervalInSeconds { get; set; } = 600f;
            public float GameDurationInSeconds { get; set; } = 25f;
            public int MinimumPlayers { get; set; } = 1;

            public Dictionary<int , string> MathExpressions { get; set; } = new Dictionary<int , string>
            {
                [0] = "({X} * {Y}) + {Z}" ,
                [1] = "{X} * ({Y} - {Z})" ,
                [2] = "{X} * ({Y} + {Z})" ,
                [3] = "{X} + ({Y} * {Z})" ,
                [4] = "{X} - ({Y} * {Z})" ,
                [5] = "({X} * {Y}) - {Z}" ,
                [6] = "{X} * {Y} * {Z}" ,
            };

            public List<Reward> Rewards { get; set; }
        }

        protected override void LoadDefaultConfig()
        {
            _config = new Configuration
            {
                Rewards = new List<Reward>
                {
                    new Reward
                    {
                        ItemShortname = "ammo.pistol",
                        SkinId = 0,
                        CustomName = "",
                        MinQuantity = 5,
                        MaxQuantity = 20
                    },
                    new Reward
                    {
                        ItemShortname = "ammo.rifle",
                        SkinId = 0,
                        CustomName = "",
                        MinQuantity = 10,
                        MaxQuantity = 30
                    },
                    new Reward
                    {
                        ItemShortname = "wood",
                        SkinId = 0,
                        CustomName = "",
                        MinQuantity = 100,
                        MaxQuantity = 500
                    },
                    new Reward
                    {
                        ItemShortname = "metal.fragments",
                        SkinId = 0,
                        CustomName = "",
                        MinQuantity = 50,
                        MaxQuantity = 200
                    },
                    new Reward
                    {
                        ItemShortname = "lmg.m249",
                        SkinId = 0,
                        CustomName = "",
                        MinQuantity = 1,
                        MaxQuantity = 1
                    },
                }
            };
            Puts("Default configuration loaded.");
        }

        protected override void SaveConfig() => Config.WriteObject(_config);
        #endregion

        #region Hooks
        private void Init()
        {
            try
            {
                _config = Config.ReadObject<Configuration>();
            }
            catch
            {
                Puts("Failed to load config file. Using default configuration...");
                LoadDefaultConfig();
            }

            if (_config == null)
            {
                LoadDefaultConfig();
            }

            if (_config.Rewards == null)
            {
                _config.Rewards = new List<Reward>();
            }

            SaveConfig();
            _playersWhoAnswered = new List<ulong>();
            _nextGameTime = Time.realtimeSinceStartup + _config.GameIntervalInSeconds;
        }

        private void Loaded()
        {
            permission.RegisterPermission(AdminPermission , this);
            if (_config.UseServerRewards && ServerRewards == null)
            {
                PrintError("ServerRewards.cs is not present. Change your config option to disable RP rewards and reload TellMeX.");
            }
            if (_config.UseEconomics && Economics == null)
            {
                PrintError("Economics.cs is not present. Change your config option to disable $ rewards and reload TellMeX.");
            }
            if ((_config.UseBattlepass || _config.UseBattlepassFirstCurrency || _config.UseBattlepassSecondCurrency) && Battlepass == null)
            {
                PrintError("Battlepass is not installed. Change your config options to disable Battlepass settings and reload TellMeX.");
            }
        }

        private void Unload()
        {
            _playersWhoAnswered?.Clear();
            _gameIsActive = false;
        }

        private void OnTick()
        {
            if (!_gameIsActive && Time.realtimeSinceStartup > _nextGameTime)
            {
                if (BasePlayer.activePlayerList.Count >= _config.MinimumPlayers)
                {
                    StartTellMeX();
                }
            }
            if (_gameIsActive && Time.realtimeSinceStartup > _gameEndTime)
            {
                GameExpired();
            }
        }
        #endregion

        #region Commands
        [ChatCommand("x")]
        private void CmdX(BasePlayer player , string command , string[] args)
        {
            if (args.Length == 0)
            {
                HandlePlayerGuessOrTimer(player , null);
                return;
            }

            switch (args[0].ToLower())
            {
                case "start":
                    HandleAdminStart(player);
                    break;
                default:
                    HandlePlayerGuessOrTimer(player , args[0]);
                    break;
            }
        }

        [ConsoleCommand("x.start")]
        private void CmdConsoleStart(ConsoleSystem.Arg arg)
        {
            if (arg.Player() != null)
            {
                BasePlayer player = arg.Player();
                if (!permission.UserHasPermission(player.UserIDString , AdminPermission))
                {
                    arg.ReplyWith(lang.GetMessage("NoPermission" , this , player.UserIDString));
                    return;
                }

                StartTellMeX();
                arg.ReplyWith(lang.GetMessage("AdminStartEvent" , this , player.UserIDString));
                return;
            }

            StartTellMeX();
            Puts(lang.GetMessage("AdminStartEvent" , this));
        }
        #endregion

        #region Game Logic
        private void HandleAdminStart(BasePlayer player)
        {
            if (!permission.UserHasPermission(player.UserIDString , AdminPermission))
            {
                string message = lang.GetMessage("NoPermission" , this , player.UserIDString);
                Player.Message(player , message , _config.Prefix , _config.SteamIDIcon);
                return;
            }
            StartTellMeX();
            string startMessage = lang.GetMessage("AdminStartEvent" , this , player.UserIDString);
            Player.Message(player , startMessage , _config.Prefix , _config.SteamIDIcon);
        }

        private void HandlePlayerGuessOrTimer(BasePlayer player , string guess)
        {
            if (string.IsNullOrEmpty(guess))
            {
                if (!_gameIsActive)
                {
                    float timeToNextGame = _nextGameTime - Time.realtimeSinceStartup;
                    string message = string.Format(lang.GetMessage("NextTellMeXMsg" , this , player.UserIDString) , Mathf.CeilToInt(timeToNextGame));
                    Player.Message(player , message , _config.Prefix , _config.SteamIDIcon);
                }
                else
                {
                    float timeRemaining = _gameEndTime - Time.realtimeSinceStartup;
                    string message = lang.GetMessage("CommandTellMeXMsg" , this , player.UserIDString) + "\nTime remaining: " + Mathf.CeilToInt(timeRemaining) + " seconds";
                    Player.Message(player , message , _config.Prefix , _config.SteamIDIcon);
                }
                return;
            }

            if (!_gameIsActive)
            {
                float timeToNextGame = _nextGameTime - Time.realtimeSinceStartup;
                string message = string.Format(lang.GetMessage("NextTellMeXMsg" , this , player.UserIDString) , Mathf.CeilToInt(timeToNextGame));
                Player.Message(player , message , _config.Prefix , _config.SteamIDIcon);
                return;
            }

            if (_playersWhoAnswered.Contains(player.userID))
            {
                float timeRemaining = _gameEndTime - Time.realtimeSinceStartup;
                string message = string.Format(lang.GetMessage("AlreadyTellMeXMsg" , this , player.UserIDString) , Mathf.CeilToInt(timeRemaining));
                Player.Message(player , message , _config.Prefix , _config.SteamIDIcon);
                return;
            }

            if (!int.TryParse(guess , out int playerGuess))
            {
                string message = lang.GetMessage("NotNumericTellMeXMsg" , this , player.UserIDString);
                Player.Message(player , message , _config.Prefix , _config.SteamIDIcon);
                return;
            }

            if (guess == _correctAnswer)
            {
                _gameIsActive = false;
                _nextGameTime = Time.realtimeSinceStartup + _config.GameIntervalInSeconds;
                if (_selectedReward != null) GivePlayerGift(player , _selectedReward , _selectedQuantity);

                string winMessage = GiveRewards(player , true);
                Server.Broadcast(winMessage , _config.Prefix , _config.SteamIDIcon);

                if (_config.UseGUIAnnouncement && GUIAnnouncements != null)
                {
                    GUIAnnouncements.Call("CreateAnnouncement" , winMessage , "blue" , "yellow");
                }

                BroadcastMath(false);
                _playersWhoAnswered.Clear();
            }
            else
            {
                string loseMessage = GiveRewards(player , false);
                Player.Message(player , loseMessage , _config.Prefix , _config.SteamIDIcon);
                _playersWhoAnswered.Add(player.userID);
            }
        }

        private void StartTellMeX()
        {
            if (_config.Rewards == null || _config.Rewards.Count == 0)
            {
                PrintWarning("No rewards defined in the configuration! Please add some rewards to the list to start the game.");
                _gameIsActive = false;
                _nextGameTime = Time.realtimeSinceStartup + _config.GameIntervalInSeconds;
                return;
            }

            _gameIsActive = true;
            _playersWhoAnswered.Clear();
            _gameEndTime = Time.realtimeSinceStartup + _config.GameDurationInSeconds;

            int x = UnityEngine.Random.Range(2 , 20);
            int y = UnityEngine.Random.Range(2 , 20);
            int z = UnityEngine.Random.Range(2 , 20);
            int expressionIndex = UnityEngine.Random.Range(0 , _config.MathExpressions.Count);

            _mathExpression = _config.MathExpressions[expressionIndex]
                .Replace("{X}" , x.ToString())
                .Replace("{Y}" , y.ToString())
                .Replace("{Z}" , z.ToString());

            _correctAnswer = DoMath(x , y , z , expressionIndex);

            _selectedReward = _config.Rewards[UnityEngine.Random.Range(0 , _config.Rewards.Count)];
            _selectedQuantity = UnityEngine.Random.Range(_selectedReward.MinQuantity , _selectedReward.MaxQuantity + 1);

            if (string.IsNullOrEmpty(_correctAnswer))
            {
                PrintWarning($"Math Error! Expression Index {expressionIndex} with X={x}, Y={y}, Z={z}");
                StartTellMeX();
                return;
            }

            BroadcastMath(true);
            Puts($"TellMeX started: {_mathExpression} = {_correctAnswer}");
        }

        private void GameExpired()
        {
            string message = lang.GetMessage("ExpiredTellMeXMsg" , this);
            Server.Broadcast($"{_correctAnswer} {message}" , _config.Prefix , _config.SteamIDIcon);
            _gameIsActive = false;
            _nextGameTime = Time.realtimeSinceStartup + _config.GameIntervalInSeconds;
            _playersWhoAnswered.Clear();
        }

        private string DoMath(int x , int y , int z , int index)
        {
            switch (index)
            {
                case 0: return (x * y + z).ToString();
                case 1: return (x * (y - z)).ToString();
                case 2: return (x * (y + z)).ToString();
                case 3: return (x + y * z).ToString();
                case 4: return (x - y * z).ToString();
                case 5: return (x * y - z).ToString();
                case 6: return (x * y * z).ToString();
                default: return string.Empty;
            }
        }

        private string GiveRewards(BasePlayer player , bool isWinner)
        {
            List<string> rewards = new List<string>();
            string message;

            if (isWinner)
            {
                string rewardName = (_selectedReward != null && !string.IsNullOrEmpty(_selectedReward.CustomName)) ? _selectedReward.CustomName : (_selectedReward != null ? _selectedReward.ItemShortname : "no item");
                string rewardDetails = $"[{_selectedQuantity} x {rewardName}]";
                string formatString = lang.GetMessage("WonTellMeXMsg" , this , player.UserIDString);
                message = string.Format(formatString , player.displayName , rewardDetails);

                if (_config.UseServerRewards && ServerRewards != null)
                {
                    ServerRewards?.Call("AddPoints" , player.userID , _config.PointsOnWin);
                    rewards.Add($"[{_config.PointsOnWin} RP]");
                }
                if (_config.UseEconomics && Economics != null)
                {
                    Economics?.Call("Deposit" , player.userID , (double)_config.PointsOnWin);
                    rewards.Add($"[{_config.PointsOnWin} $]");
                }
                if (_config.UseBattlepass && Battlepass != null)
                {
                    if (_config.UseBattlepassFirstCurrency)
                    {
                        Battlepass?.Call("AddFirstCurrency" , player.userID , _config.BattlepassWinReward1);
                        rewards.Add($"[{_config.BattlepassWinReward1} BP1]");
                    }
                    if (_config.UseBattlepassSecondCurrency)
                    {
                        Battlepass?.Call("AddSecondCurrency" , player.userID , _config.BattlepassWinReward2);
                        rewards.Add($"[{_config.BattlepassWinReward2} BP2]");
                    }
                }
            }
            else
            {
                string rewardsText = "";

                if (_config.UseServerRewards)
                {
                    ServerRewards?.Call("AddPoints" , player.userID , _config.PointsOnLoss);
                    rewards.Add($"[{_config.PointsOnLoss} RP]");
                }
                if (_config.UseEconomics)
                {
                    Economics?.Call("Deposit" , player.userID , (double)_config.PointsOnLoss);
                    rewards.Add($"[{_config.PointsOnLoss} $]");
                }
                if (_config.UseBattlepassOnLoss)
                {
                    if (_config.UseBattlepassFirstCurrency)
                    {
                        Battlepass?.Call("AddFirstCurrency" , player.userID , _config.BattlepassLossReward1);
                        rewards.Add($"[{_config.BattlepassLossReward1} BP1]");
                    }
                    if (_config.UseBattlepassSecondCurrency)
                    {
                        Battlepass?.Call("AddSecondCurrency" , player.userID , _config.BattlepassLossReward2);
                        rewards.Add($"[{_config.BattlepassLossReward2} BP2]");
                    }
                }

                if (rewards.Count > 0)
                {
                    rewardsText = string.Join(" + " , rewards);
                }

                string formatString = lang.GetMessage("LoseTellMeXMsg" , this , player.UserIDString);
                message = string.Format(formatString , rewardsText);
            }

            return message;
        }

        private void BroadcastMath(bool start)
        {
            if (start)
            {
                string startMsg = string.Format(lang.GetMessage("StartTellMeXMsg" , this , null) , _mathExpression);
                string commandMsg = lang.GetMessage("CommandTellMeXMsg" , this , null);
                Server.Broadcast($"{startMsg}\n{commandMsg}" , _config.Prefix , _config.SteamIDIcon);
            }
            else
            {
                string endMsg = string.Format(lang.GetMessage("EndTellMeXMsg" , this , null) , _correctAnswer);
                Server.Broadcast(endMsg , _config.Prefix , _config.SteamIDIcon);
            }
        }

        private void GivePlayerGift(BasePlayer player , Reward reward , int quantity)
        {
            Item item = ItemManager.CreateByName(reward.ItemShortname , quantity , reward.SkinId);
            if (item == null)
            {
                string errorMsg = lang.GetMessage("SorryErrorMsg" , this , player.UserIDString);
                Player.Message(player , errorMsg , _config.Prefix , _config.SteamIDIcon);
                return;
            }
            player.GiveItem(item);
        }
        #endregion
    }
}
