#nullable disable
using EllieBot.Common.TypeReaders;
using EllieBot.Modules.Gambling.Common;
using EllieBot.Modules.Gambling.Common.Blackjack;
using EllieBot.Modules.Gambling.Services;

namespace EllieBot.Modules.Gambling;

public partial class Gambling
{
    public partial class BlackJackCommands : GamblingSubmodule<BlackJackService>
    {
        public enum BjAction
        {
            Hit = int.MinValue,
            Stand,
            Double
        }

        private readonly ICurrencyService _cs;
        private readonly DbService _db;
        private IUserMessage msg;

        public BlackJackCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConf)
            : base(gamblingConf)
        {
            _cs = cs;
            _db = db;
        }

        [Cmd]
        [RequireContext(ContextType.Guild)]
        public async Task BlackJack([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
        {
            if (!await CheckBetMandatory(amount))
                return;

            var newBj = new Blackjack(_cs);
            Blackjack bj;
            if (newBj == (bj = _service.Games.GetOrAdd(ctx.Channel.Id, newBj)))
            {
                if (!await bj.Join(ctx.User, amount))
                {
                    _service.Games.TryRemove(ctx.Channel.Id, out _);
                    await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
                    return;
                }

                bj.StateUpdated += Bj_StateUpdated;
                bj.GameEnded += Bj_GameEnded;
                bj.Start();

                await Response().NoReply().Confirm(strs.bj_created(ctx.User.ToString())).SendAsync();
            }
            else
            {
                if (await bj.Join(ctx.User, amount))
                    await Response().NoReply().Confirm(strs.bj_joined(ctx.User.ToString())).SendAsync();
                else
                {
                    Log.Information("{User} can't join a blackjack game as it's in {BlackjackState} state already",
                        ctx.User,
                        bj.State);
                }
            }

            await ctx.Message.DeleteAsync();
        }

        private Task Bj_GameEnded(Blackjack arg)
        {
            _service.Games.TryRemove(ctx.Channel.Id, out _);
            return Task.CompletedTask;
        }

        private async Task Bj_StateUpdated(Blackjack bj)
        {
            try
            {
                if (msg is not null)
                    _ = msg.DeleteAsync();

                var c = bj.Dealer.Cards.Select(x => x.GetEmojiString())
                          .ToList();
                var dealerIcon = "❔ ";
                if (bj.State == Blackjack.GameState.Ended)
                {
                    if (bj.Dealer.GetHandValue() == 21)
                        dealerIcon = "💰 ";
                    else if (bj.Dealer.GetHandValue() > 21)
                        dealerIcon = "💥 ";
                    else
                        dealerIcon = "🏁 ";
                }

                var cStr = string.Concat(c.Select(x => x[..^1] + " "));
                cStr += "\n" + string.Concat(c.Select(x => x.Last() + " "));
                var embed = _sender.CreateEmbed()
                               .WithOkColor()
                               .WithTitle("BlackJack")
                               .AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr);

                if (bj.CurrentUser is not null)
                    embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser}");

                foreach (var p in bj.Players)
                {
                    c = p.Cards.Select(x => x.GetEmojiString()).ToList();
                    cStr = "-\t" + string.Concat(c.Select(x => x[..^1] + " "));
                    cStr += "\n-\t" + string.Concat(c.Select(x => x.Last() + " "));
                    var full = $"{p.DiscordUser.ToString().TrimTo(20)} | Bet: {N(p.Bet)} | Value: {p.GetHandValue()}";
                    if (bj.State == Blackjack.GameState.Ended)
                    {
                        if (p.State == User.UserState.Lost)
                            full = "❌ " + full;
                        else
                            full = "✅ " + full;
                    }
                    else if (p == bj.CurrentUser)
                        full = "▶ " + full;
                    else if (p.State == User.UserState.Stand)
                        full = "⏹ " + full;
                    else if (p.State == User.UserState.Bust)
                        full = "💥 " + full;
                    else if (p.State == User.UserState.Blackjack)
                        full = "💰 " + full;

                    embed.AddField(full, cStr);
                }

                msg = await Response().Embed(embed).SendAsync();
            }
            catch
            {
            }
        }

        private string UserToString(User x)
        {
            var playerName = x.State == User.UserState.Bust
                ? Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30))
                : x.DiscordUser.ToString();

            // var hand = $"{string.Concat(x.Cards.Select(y => "〖" + y.GetEmojiString() + "〗"))}";


            return $"{playerName} | Bet: {x.Bet}\n";
        }

        [Cmd]
        [RequireContext(ContextType.Guild)]
        public Task Hit()
            => InternalBlackJack(BjAction.Hit);

        [Cmd]
        [RequireContext(ContextType.Guild)]
        public Task Stand()
            => InternalBlackJack(BjAction.Stand);

        [Cmd]
        [RequireContext(ContextType.Guild)]
        public Task Double()
            => InternalBlackJack(BjAction.Double);

        private async Task InternalBlackJack(BjAction a)
        {
            if (!_service.Games.TryGetValue(ctx.Channel.Id, out var bj))
                return;

            if (a == BjAction.Hit)
                await bj.Hit(ctx.User);
            else if (a == BjAction.Stand)
                await bj.Stand(ctx.User);
            else if (a == BjAction.Double)
            {
                if (!await bj.Double(ctx.User))
                    await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
            }

            await ctx.Message.DeleteAsync();
        }
    }
}