using Discord.WebSocket;
using NadekoBot.Modules.Gambling.Common;
using NadekoBot.Services;
using NadekoBot.Modules.Gambling.Common.Connect4;
using NadekoBot.Modules.Gambling.Common.WheelOfFortune;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Common;
using NadekoBot.Db;
using NadekoBot.Modules.Gambling.Common.Slot;
using NadekoBot.Modules.Gambling.Services;
using Serilog;

namespace NadekoBot.Modules.Gambling.Services
{
    public class GamblingService : INService
    {
        private readonly DbService _db;
        private readonly ICurrencyService _cs;
        private readonly Bot _bot;
        private readonly DiscordSocketClient _client;
        private readonly IDataCache _cache;
        private readonly GamblingConfigService _gss;

        public ConcurrentDictionary<(ulong, ulong), RollDuelGame> Duels { get; } = new ConcurrentDictionary<(ulong, ulong), RollDuelGame>();
        public ConcurrentDictionary<ulong, Connect4Game> Connect4Games { get; } = new ConcurrentDictionary<ulong, Connect4Game>();

        private readonly Timer _decayTimer;

        public GamblingService(DbService db, Bot bot, ICurrencyService cs,
            DiscordSocketClient client, IDataCache cache, GamblingConfigService gss)
        {
            _db = db;
            _cs = cs;
            _bot = bot;
            _client = client;
            _cache = cache;
            _gss = gss;
            
            if (_bot.Client.ShardId == 0)
            {
                _decayTimer = new Timer(_ =>
                {
                    var config = _gss.Data;
                    var maxDecay = config.Decay.MaxDecay;
                    if (config.Decay.Percent <= 0 || config.Decay.Percent > 1 || maxDecay < 0)
                        return;

                    using (var uow = _db.GetDbContext())
                    {
                        var lastCurrencyDecay = _cache.GetLastCurrencyDecay();
                        
                        if (DateTime.UtcNow - lastCurrencyDecay < TimeSpan.FromHours(config.Decay.HourInterval))
                           return;
                        
                         Log.Information($"Decaying users' currency - decay: {config.Decay.Percent * 100}% " +
                                   $"| max: {maxDecay} " +
                                   $"| threshold: {config.Decay.MinThreshold}");
                         
                         if (maxDecay == 0)
                             maxDecay = int.MaxValue;
                         
                        uow.Database.ExecuteSqlInterpolated($@"
UPDATE DiscordUser
SET CurrencyAmount=
    CASE WHEN
    {maxDecay} > ROUND(CurrencyAmount * {config.Decay.Percent} - 0.5)
    THEN
    CurrencyAmount - ROUND(CurrencyAmount * {config.Decay.Percent} - 0.5)
    ELSE
    CurrencyAmount - {maxDecay}
    END
WHERE CurrencyAmount > {config.Decay.MinThreshold} AND UserId!={_client.CurrentUser.Id};");

                        _cache.SetLastCurrencyDecay();
                        uow.SaveChanges();
                    }
                }, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
            }
        }
        
        public async Task<SlotResponse> SlotAsync(ulong userId, long amount)
        {
            var takeRes = await _cs.RemoveAsync(userId, "Slot Machine", amount, true);
            
            if (!takeRes)
            {
                return new SlotResponse
                {
                    Error = GamblingError.NotEnough
                };
            }

            var game = new SlotGame();
            var result = game.Spin();
            long won = 0;

            if (result.Multiplier > 0)
            {
                won = (long)(result.Multiplier * amount);

                await _cs.AddAsync(userId, $"Slot Machine x{result.Multiplier}", won, true);
            }

            var toReturn = new SlotResponse
            {
                Multiplier = result.Multiplier,
                Won = won,
            };

            toReturn.Rolls.AddRange(result.Rolls);

            return toReturn;
        }


        public struct EconomyResult
        {
            public decimal Cash { get; set; }
            public decimal Planted { get; set; }
            public decimal Waifus { get; set; }
            public decimal OnePercent { get; set; }
            public long Bot { get; set; }
        }

        public EconomyResult GetEconomy()
        {
            if (_cache.TryGetEconomy(out var data))
            {
                try
                {
                    return JsonConvert.DeserializeObject<EconomyResult>(data);
                }
                catch { }
            }

            decimal cash;
            decimal onePercent;
            decimal planted;
            decimal waifus;
            long bot;

            using (var uow = _db.GetDbContext())
            {
                cash = uow.DiscordUser.GetTotalCurrency();
                onePercent = uow.DiscordUser.GetTopOnePercentCurrency(_client.CurrentUser.Id);
                planted = uow.PlantedCurrency.AsQueryable().Sum(x => x.Amount);
                waifus = uow.WaifuInfo.GetTotalValue();
                bot = uow.DiscordUser.GetUserCurrency(_client.CurrentUser.Id);
            }

            var result = new EconomyResult
            {
                Cash = cash,
                Planted = planted,
                Bot = bot,
                Waifus = waifus,
                OnePercent = onePercent,
            };

            _cache.SetEconomy(JsonConvert.SerializeObject(result));
            return result;
        }

        public Task<WheelOfFortuneGame.Result> WheelOfFortuneSpinAsync(ulong userId, long bet)
        {
            return new WheelOfFortuneGame(userId, bet, _gss.Data, _cs).SpinAsync();
        }
    }
}
