﻿using System;
using System.Collections.Generic;
using System.Linq;
using Oxide.Core.Plugins;
using Oxide.Core;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("TellMeC" , "Krungh Crow" , "2.0.0")]
    [Description("Guess the correct color display combo and get rewards")]
    public class TellMeC : RustPlugin
    {
        [PluginReference]
        Plugin Battlepass, ServerRewards, Economics, GUIAnnouncements;

        private const string AdminPermission = "tellmec.admin";

        #region Variables & Localization

        private bool isGameActive;
        private string winningColorName;
        private string winningColorHex;
        private List<ulong> playersWhoHavePlayed;
        private string rewardItemName;
        private ulong rewardItemSkin;
        private int rewardItemQuantity;
        private Timer gameTimer;
        private Timer gameScheduler;
        private DateTime nextGameStartTime;

        private Dictionary<string , string> colorMap = new Dictionary<string , string>
        {
            { "#2d2d2d", "black" },
            { "#00c5e8", "blue" },
            { "#54ff68", "green" },
            { "#a3a3a3", "grey" },
            { "#ffb163", "orange" },
            { "#bf64fc", "purple" },
            { "#ff726b", "red" },
            { "white", "white" },
            { "#fffc75", "yellow" },
        };

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string , string>
            {
                {"StartMessage", "Find the correct color match you see!\n(ex: <color=yellow>/color white</color>)"},
                {"NextGameMessage", "The game restarts in {0} seconds."},
                {"AlreadyPlayedMessage", "You have already played this round!"},
                {"InvalidEntryMessage", "Invalid entry. Try something like /color white"},
                {"WinMessage", "found the color match and has won: "},
                {"CorrectColorWasMessage", "The correct color match was {0}"},
                {"ExpiredMessage", "was not found in time!"},
                {"LoseMessage", "This is NOT the correct color match... for trying you won "},
                {"LoseMessageNoReward", "This is NOT the correct color match... for trying you won nothing this time."},
                {"ErrorMessage", "Sorry, an error has occurred! Please report this. Item to give was null."},
                {"NoPermission", "You don't have permission to use this command."},
                {"GameIsActive", "The color game is already active."},
                {"GameStarted", "Manual game started!"},
            } , this , "en");
        }

        #endregion

        #region Configuration

        private Configuration config;

        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; } = "<color=purple>[Color Game]</color> ";
            public ulong SteamIDIcon { get; set; } = 76561198842641699;
            public string WinnerColor { get; set; } = "yellow";
            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 float GameRateInSeconds { get; set; } = 600f;
            public float GameLengthInSeconds { get; set; } = 25f;
            public int MinPlayersOnline { get; set; } = 1;
            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 List<Reward> Rewards { get; set; }
        }

        protected override void LoadConfig()
        {
            base.LoadConfig();
            try
            {
                config = Config.ReadObject<Configuration>();
                if (config == null) throw new Exception();
                SaveConfig();
            }
            catch
            {
                Puts("Failed to load config file. Creating new configuration...");
                config = new Configuration();
            }
            SaveConfig();
        }

        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
                    },
                }
            };
        }

        protected override void SaveConfig() => Config.WriteObject(config);

        #endregion

        #region Hooks

        private void Init()
        {
            permission.RegisterPermission(AdminPermission , this);
            playersWhoHavePlayed = new List<ulong>();
            isGameActive = false;
        }

        private void OnServerInitialized()
        {
            ValidateConfiguration();
            ScheduleNextGame();
        }

        private void Unload()
        {
            gameTimer?.Destroy();
            gameScheduler?.Destroy();
        }

        private void OnPluginLoaded(Plugin plugin)
        {
            if (plugin.Name == "ServerRewards" && config.UseServerRewards) Puts("ServerRewards is now loaded.");
            if (plugin.Name == "Economics" && config.UseEconomics) Puts("Economics is now loaded.");
            if (plugin.Name == "Battlepass" && config.UseBattlepass) Puts("Battlepass is now loaded.");
            if (plugin.Name == "GUIAnnouncements" && config.UseGuiAnnouncement) Puts("GUIAnnouncements is now loaded.");
        }

        #endregion

        #region Game Logic

        private void ValidateConfiguration()
        {
            if (config.GameLengthInSeconds >= config.GameRateInSeconds)
            {
                PrintError("Game length is bigger than game rate. Please change your config options and reload.");
            }
        }

        private void ScheduleNextGame()
        {
            gameScheduler?.Destroy();
            gameScheduler = timer.Once(config.GameRateInSeconds , () =>
            {
                if (BasePlayer.activePlayerList.Count >= config.MinPlayersOnline)
                {
                    StartGame();
                }
                else
                {
                    ScheduleNextGame();
                }
            });
            nextGameStartTime = DateTime.Now.AddSeconds(config.GameRateInSeconds);
        }

        private void StartGame()
        {
            isGameActive = true;
            playersWhoHavePlayed.Clear();

            var randomKey = colorMap.ElementAt(Core.Random.Range(0 , colorMap.Count));
            winningColorHex = randomKey.Key;
            winningColorName = randomKey.Value;

            var mixedColors = new List<string>();
            for (int i = 0; i < 15; i++)
            {
                var hex = colorMap.ElementAt(Core.Random.Range(0 , colorMap.Count)).Key;
                var name = colorMap.ElementAt(Core.Random.Range(0 , colorMap.Count)).Value;

                while (colorMap.TryGetValue(hex , out string value) && value.Equals(name , StringComparison.OrdinalIgnoreCase))
                {
                    hex = colorMap.ElementAt(Core.Random.Range(0 , colorMap.Count)).Key;
                    name = colorMap.ElementAt(Core.Random.Range(0 , colorMap.Count)).Value;
                }
                mixedColors.Add($"<color={hex}>{name}</color>");
            }

            mixedColors.Insert(Core.Random.Range(0 , mixedColors.Count) , $"<color={winningColorHex}>{winningColorName}</color>");

            var message = lang.GetMessage("StartMessage" , this) + "\n";
            message += string.Join(" , " , mixedColors.Take(8).ToArray()) + "\n";
            message += string.Join(" , " , mixedColors.Skip(8).ToArray());

            Server.Broadcast(message , config.Prefix , config.SteamIDIcon);
            Puts($"Color Game has started. The correct color is: {winningColorName}");

            if (config.Rewards.Count > 0)
            {
                var randomReward = config.Rewards.ElementAt(Core.Random.Range(0 , config.Rewards.Count));
                rewardItemName = string.IsNullOrEmpty(randomReward.CustomName) ? randomReward.ItemShortname : randomReward.CustomName;
                rewardItemSkin = randomReward.SkinId;
                rewardItemQuantity = Core.Random.Range(randomReward.MinQuantity , randomReward.MaxQuantity + 1); // Max is exclusive
            }

            gameTimer = timer.Once(config.GameLengthInSeconds , EndGameExpired);
        }

        private void EndGameExpired()
        {
            if (!isGameActive) return;

            isGameActive = false;
            Server.Broadcast(string.Format(lang.GetMessage("CorrectColorWasMessage" , this) , $"<color={winningColorHex}>{winningColorName}</color>") , config.Prefix , config.SteamIDIcon);
            ScheduleNextGame();
        }

        #endregion

        #region Commands

        [ChatCommand("color")]
        void CmdColorMain(BasePlayer player , string command , string[] args)
        {
            if (args.Length == 0)
            {
                HandlePlayerGuess(player , null);
                return;
            }

            switch (args[0].ToLower())
            {
                case "start":
                    HandleAdminStart(player);
                    break;
                default:
                    HandlePlayerGuess(player , args[0]);
                    break;
            }
        }

        [ConsoleCommand("color.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;
                }

                StartGame();
                arg.ReplyWith(lang.GetMessage("AdminStartEvent" , this , player.UserIDString));
                return;
            }

            StartGame();
            Puts(lang.GetMessage("AdminStartEvent" , this));
        }

        private void HandlePlayerGuess(BasePlayer player , string guess)
        {
            if (!isGameActive)
            {
                TimeSpan timeUntilNextGame = nextGameStartTime - DateTime.Now;
                int timeRemaining = (int)Math.Ceiling(timeUntilNextGame.TotalSeconds);

                if (timeRemaining > 0)
                {
                    Player.Message(player , string.Format(lang.GetMessage("NextGameMessage" , this) , timeRemaining) , config.Prefix , config.SteamIDIcon);
                }
                else
                {
                    Player.Message(player , string.Format(lang.GetMessage("NextGameMessage" , this) , "a few") , config.Prefix , config.SteamIDIcon);
                }
                return;
            }

            if (playersWhoHavePlayed.Contains(player.userID))
            {
                Player.Message(player , lang.GetMessage("AlreadyPlayedMessage" , this) , config.Prefix , config.SteamIDIcon);
                return;
            }

            if (string.IsNullOrEmpty(guess))
            {
                Player.Message(player , lang.GetMessage("InvalidEntryMessage" , this) , config.Prefix , config.SteamIDIcon);
                return;
            }

            playersWhoHavePlayed.Add(player.userID);

            if (guess.Equals(winningColorName , StringComparison.OrdinalIgnoreCase))
            {
                gameTimer?.Destroy();
                isGameActive = false;

                GivePlayerGift(player , rewardItemName , rewardItemSkin , rewardItemQuantity);
                string rewardMessage = GetRewardMessage(player , true);

                Server.Broadcast($"<color={config.WinnerColor}>{player.displayName}</color> {lang.GetMessage("WinMessage" , this)}{rewardMessage}" , config.Prefix , config.SteamIDIcon);
                if (config.UseGuiAnnouncement)
                {
                    GUIAnnouncements?.Call("CreateAnnouncement" , $"{player.displayName} {lang.GetMessage("WinMessage" , this)}{rewardMessage}" , "blue" , "yellow");
                }

                Server.Broadcast(string.Format(lang.GetMessage("CorrectColorWasMessage" , this) , $"<color={winningColorHex}>{winningColorName}</color>") , config.Prefix , config.SteamIDIcon);
                ScheduleNextGame();
            }
            else
            {
                string rewardMessage = GetRewardMessage(player , false);
                if (string.IsNullOrEmpty(rewardMessage))
                {
                    Player.Message(player , lang.GetMessage("LoseMessageNoReward" , this) , config.Prefix , config.SteamIDIcon);
                }
                else
                {
                    Player.Message(player , $"{lang.GetMessage("LoseMessage" , this)}{rewardMessage}" , config.Prefix , config.SteamIDIcon);
                }
            }
        }

        private void HandleAdminStart(BasePlayer player)
        {
            if (!player.IsAdmin && !permission.UserHasPermission(player.UserIDString , AdminPermission))
            {
                Player.Message(player , lang.GetMessage("NoPermission" , this) , config.Prefix , config.SteamIDIcon);
                return;
            }

            if (isGameActive)
            {
                Player.Message(player , lang.GetMessage("GameIsActive" , this) , config.Prefix , config.SteamIDIcon);
                return;
            }

            gameScheduler?.Destroy();
            StartGame();
            Player.Message(player , lang.GetMessage("GameStarted" , this) , config.Prefix , config.SteamIDIcon);
        }

        #endregion

        #region Reward Handling

        private string GetRewardMessage(BasePlayer player , bool isWin)
        {
            List<string> rewards = new List<string>();

            if (isWin)
            {
                // We use the rewardItemName and rewardItemQuantity set in StartGame
                rewards.Add($"[{rewardItemQuantity} x {rewardItemName}]");

                if (config.UseServerRewards)
                {
                    ServerRewards?.Call("AddPoints" , player.userID , config.PointsOnWin);
                    rewards.Add($"[{config.PointsOnWin} RP]");
                }
                if (config.UseEconomics)
                {
                    Economics?.Call("Deposit" , player.userID , (double)config.PointsOnWin);
                    rewards.Add($"[{config.PointsOnWin} $]");
                }

                if (config.UseBattlepass)
                {
                    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
            {
                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.UseBattlepass && 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)
            {
                return "";
            }

            return " " + string.Join(" + " , rewards);
        }

        private void GivePlayerGift(BasePlayer player , string gift , ulong skin , int quantity)
        {
            var itemDefinition = ItemManager.FindItemDefinition(gift);
            if (itemDefinition == null)
            {
                Player.Message(player , string.Format(lang.GetMessage("ErrorMessage" , this)) , config.Prefix , config.SteamIDIcon);
                return;
            }

            Item item = ItemManager.CreateByItemID(itemDefinition.itemid , quantity , skin);
            player.GiveItem(item);
        }

        #endregion
    }
}
