﻿using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using UnityEngine;
/*
 * Fixed payouts on re-spawns always being negative values.
 * Fixed save data bug not being saved on unload.
 * Rewrote code to make more performant & work correctly.
 * Added config toggle StackMultipliers
 * Updated deposit hook.
 * Fixed base payout from config addition.
 */
namespace Oxide.Plugins
{
    [Info("Money Time", "Wulf", "2.3.0")]
    [Description("Pays players with Economics money for playing")]
    public class MoneyTime : CovalencePlugin
    {
        #region Configuration

        private const string Perm = "moneytime.";
        private Configuration _config;

        public class Configuration
        {
            // TODO: Add option for daily/weekly login bonuses

            [JsonProperty("Enable Economics as default currency")]
            public bool Economics = true;

            [JsonProperty("Enable Server Rewards as default currency")]
            public bool ServerRewards = false;

            [JsonProperty("Enable AFK API plugin support")]
            public bool AfkApi = false;

            [JsonProperty("Base payout amount")]
            public int BasePayout = 100;

            [JsonProperty("Payout interval (seconds)")]
            public int PayoutInterval = 600;

            [JsonProperty("Time alive bonus")]
            public bool TimeAliveBonus = false;

            [JsonProperty("Time alive multiplier")]
            public float TimeAliveMultiplier = 0f;

            [JsonProperty("Allow Permission-based Multipliers to stack")]
            public bool StackMultipliers = false;

            [JsonProperty("New player welcome bonus")]
            public float WelcomeBonus = 500f;

            [JsonProperty("Permission-based mulitipliers", ObjectCreationHandling = ObjectCreationHandling.Replace)]
            public SortedDictionary<string, float> PermissionMulitipliers = new SortedDictionary<string, float>
            {
                ["vip"] = 5f,
                ["donor"] = 2.5f
            };

            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()
        {
            Log($"Configuration changes saved to {Name}.json");
            Config.WriteObject(_config, true);
        }

        #endregion Configuration

        #region Localization

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["MissedPayout"] = "You have been inactive and have missed this payout",
                ["ReceivedForPlaying"] = "You have received $payout.amount for actively playing",
                ["ReceivedForTimeAlive"] = "You have received $payout.amount for staying alive for $time.alive",
                ["ReceivedWelcomeBonus"] = "You have received $payout.amount as a welcome bonus"
            }, this);
        }

        #endregion Localization

        #region Data Storage

        private StoredData _storedData;

        private class StoredData
        {
            public Dictionary<string, PlayerInfo> Players = new Dictionary<string, PlayerInfo>();

            public StoredData()
            {
            }
        }

        private class PlayerInfo
        {
            public DateTime LastTimeAlive;
            public bool WelcomeBonus;

            public PlayerInfo()
            {
                LastTimeAlive = DateTime.Now;
                WelcomeBonus = true;
            }
        }

        private void SaveData() => Interface.Oxide.DataFileSystem.WriteObject(Name, _storedData);

        private void OnServerSave() => SaveData();

        #endregion Data Storage

        #region Initialization

        [PluginReference]
        private Plugin Economics, ServerRewards, AFKAPI;

        // ID | Amount | Time
        private Dictionary<string, Values> _payOut = new Dictionary<string, Values>();
        private Dictionary<string, double> _perms = new Dictionary<string, double>();

        private class Values
        {
            public double amount;
            public float time;
        }

        private void Init()
        {
            foreach (KeyValuePair<string, float> perm in _config.PermissionMulitipliers)
            {
                string p = Perm + perm.Key;
                _perms.Add(p, perm.Value);
                permission.RegisterPermission(p, this);
                Log($"Registered permission '{p}'; multiplier {perm.Value}");
            }

            _storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>(Name);

            if (!_config.TimeAliveBonus)
                Unsubscribe(nameof(OnUserRespawn));
        }

        private void InitializePlayer(IPlayer player, float current)
        {
            string id = player.Id;
            if (!_storedData.Players.ContainsKey(id))
                _storedData.Players.Add(id, new PlayerInfo());

            double amt = _config.BasePayout;
            double multi = 0;
            if (_config.StackMultipliers)
            {
                foreach (var perm in _perms)
                    if (player.HasPermission($"{perm.Key}"))
                        multi += amt * perm.Value;

                if (multi != 0) 
                    amt = multi;
            }
            else
            {
                foreach (var perm in _perms)
                    if (player.HasPermission($"{perm.Key}") && perm.Value > multi)
                        multi = perm.Value;

                if (multi != 0)
                    amt *= multi;
            }

            if (!_payOut.ContainsKey(id))
                _payOut.Add(id, new Values { amount = amt, time = current });
            else
                _payOut[id].amount = amt;
        }

        private void OnServerInitialized()
        {
            var current = Time.realtimeSinceStartup + _config.PayoutInterval;
            foreach (IPlayer player in players.Connected)
                if (player.Id.IsSteamId()) 
                    InitializePlayer(player, current);

            timer.Every(_config.PayoutInterval, () =>
            {
                var newTime = Time.realtimeSinceStartup;
                foreach (IPlayer player in players.Connected)
                {
                    string id = player.Id;
                    if (!_payOut.ContainsKey(id)) continue;
                    var dingus = _payOut[id];
                    if (dingus.time <= newTime)
                    {
                        Payout(player, dingus.amount, GetLang("ReceivedForPlaying", id));
                        dingus.time = newTime + _config.PayoutInterval;
                    }
                }
            });
        }

        private void Unload()
        {
            SaveData();
            _perms.Clear();
            _payOut.Clear();
        }

        #endregion Initialization

        #region On Perms Updated

        private void OnGroupPermissionGranted(string name, string perm) => Edit(true, _perms.ContainsKey(perm));

        private void OnGroupPermissionRevoked(string name, string perm) => Edit(true, _perms.ContainsKey(perm));

        private void OnUserPermissionGranted(string id, string perm) => Edit(false, _perms.ContainsKey(perm), id);

        private void OnUserPermissionRevoked(string id, string perm) => Edit(false, _perms.ContainsKey(perm), id);

        private void Edit(bool all, bool mine, string user = "")
        {
            if (!mine) return;
            var current = Time.realtimeSinceStartup + _config.PayoutInterval;
            if (all)
            { 
                foreach (IPlayer player in players.Connected)
                    if (player.Id.IsSteamId()) 
                        InitializePlayer(player, current);
            }
            else
            {
                IPlayer dingus = players.FindPlayerById(user);
                InitializePlayer(dingus, current);
            }
        }

        #endregion

        #region Payout Handling

        private void Payout(IPlayer player, double amount, string message)
        {
            if (_config.AfkApi && AFKAPI != null && AFKAPI.IsLoaded)
            {
                bool isAfk = AFKAPI.Call<bool>("IsPlayerAFK", ulong.Parse(player.Id));
                if (isAfk)
                {
                    Message(player, "MissedPayout");
                    return;
                }
            }

            if (_config.Economics && Economics != null && Economics.IsLoaded)
            {
                Economics.Call("Deposit", player.Id, amount);
                Message(player, message.Replace("$payout.amount", amount.ToString()));
            }
            else if (_config.ServerRewards && ServerRewards != null && ServerRewards.IsLoaded)
            {
                ServerRewards.Call("AddPoints", player, (int)amount);
                Message(player, message.Replace("$payout.amount",amount.ToString()));
            }
        }

        private void OnUserConnected(IPlayer player)
        {
            var current = Time.realtimeSinceStartup + _config.PayoutInterval;
            InitializePlayer(player, current);

            if (_config.WelcomeBonus > 0f && !_storedData.Players[player.Id].WelcomeBonus)
                Payout(player, _config.WelcomeBonus, GetLang("ReceivedWelcomeBonus", player.Id));
        }

        private void OnUserDisconnected(IPlayer player) => _payOut.Remove(player.Id);

        private void OnUserRespawn(IPlayer player)
        {
            if (!player.Id.IsSteamId()) return;

            if (!_storedData.Players.ContainsKey(player.Id))
                InitializePlayer(player, Time.realtimeSinceStartup + _config.PayoutInterval);

            double secondsAlive = (DateTime.Now - _storedData.Players[player.Id].LastTimeAlive).TotalSeconds;
            TimeSpan timeSpan = TimeSpan.FromSeconds(secondsAlive);

            double amount = (secondsAlive / _config.BasePayout) * _config.TimeAliveMultiplier;
            string timeAlive = $"{timeSpan.TotalHours:00}h {timeSpan.Minutes:00}m {timeSpan.Seconds:00}s".TrimStart(' ', 'd', 'h', 'm', 's', '0');

            Payout(player, amount, GetLang("ReceivedForTimeAlive", player.Id).Replace("$time.alive", timeAlive));
            _storedData.Players[player.Id].LastTimeAlive = DateTime.Now;
        }

        #endregion Payout Handling

        #region Helpers

        private string GetLang(string langKey, string playerId = null) => lang.GetMessage(langKey, this, playerId);

        private void Message(IPlayer player, string textOrLang, params object[] args)
        {
            if (!player.IsConnected) return;
            string message = GetLang(textOrLang, player.Id);
            player.Reply(message != textOrLang ? message : textOrLang);
        }

        #endregion Helpers
    }
}