#nullable disable
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System.Text.RegularExpressions;
using Image = SixLabors.ImageSharp.Image;

namespace EllieBot.Modules.Gambling;

public partial class Gambling
{
    [Group]
    public partial class DiceRollCommands : EllieModule
    {
        private static readonly Regex _dndRegex = new(@"^(?<n1>\d+)d(?<n2>\d+)(?:\+(?<add>\d+))?(?:\-(?<sub>\d+))?$",
            RegexOptions.Compiled);

        private static readonly Regex _fudgeRegex = new(@"^(?<n1>\d+)d(?:F|f)$", RegexOptions.Compiled);

        private static readonly char[] _fateRolls = ['-', ' ', '+'];
        private readonly IImageCache _images;

        public DiceRollCommands(IImageCache images)
            => _images = images;

        [Cmd]
        public async Task Roll()
        {
            var rng = new EllieRandom();
            var gen = rng.Next(1, 101);

            var num1 = gen / 10;
            var num2 = gen % 10;
            
            using var img1 = await GetDiceAsync(num1);
            using var img2 = await GetDiceAsync(num2);
            using var img = new[] { img1, img2 }.Merge(out var format);
            await using var ms = await img.ToStreamAsync(format);

            var fileName = $"dice.{format.FileExtensions.First()}";

            var eb = _sender.CreateEmbed()
                .WithOkColor()
                .WithAuthor(ctx.User)
                .AddField(GetText(strs.roll2), gen)
                .WithImageUrl($"attachment://{fileName}");

            await ctx.Channel.SendFileAsync(ms,
                fileName,
                embed: eb.Build());
        }

        [Cmd]
        [Priority(1)]
        public async Task Roll(int num)
            => await InternalRoll(num, true);


        [Cmd]
        [Priority(1)]
        public async Task Rolluo(int num = 1)
            => await InternalRoll(num, false);

        [Cmd]
        [Priority(0)]
        public async Task Roll(string arg)
            => await InternallDndRoll(arg, true);

        [Cmd]
        [Priority(0)]
        public async Task Rolluo(string arg)
            => await InternallDndRoll(arg, false);

        private async Task InternalRoll(int num, bool ordered)
        {
            if (num is < 1 or > 30)
            {
                await Response().Error(strs.dice_invalid_number(1, 30)).SendAsync();
                return;
            }

            var rng = new EllieRandom();

            var dice = new List<Image<Rgba32>>(num);
            var values = new List<int>(num);
            for (var i = 0; i < num; i++)
            {
                var randomNumber = rng.Next(1, 7);
                var toInsert = dice.Count;
                if (ordered)
                {
                    if (randomNumber == 6 || dice.Count == 0)
                        toInsert = 0;
                    else if (randomNumber != 1)
                    {
                        for (var j = 0; j < dice.Count; j++)
                        {
                            if (values[j] < randomNumber)
                            {
                                toInsert = j;
                                break;
                            }
                        }
                    }
                }
                else
                    toInsert = dice.Count;

                dice.Insert(toInsert, await GetDiceAsync(randomNumber));
                values.Insert(toInsert, randomNumber);
            }

            using var bitmap = dice.Merge(out var format);
            await using var ms = bitmap.ToStream(format);
            foreach (var d in dice)
                d.Dispose();

            var imageName = $"dice.{format.FileExtensions.First()}";
            var eb = _sender.CreateEmbed()
                .WithOkColor()
                .WithAuthor(ctx.User)
                .AddField(GetText(strs.rolls), values.Select(x => Format.Code(x.ToString())).Join(' '), true)
                .AddField(GetText(strs.total), values.Sum(), true)
                .WithDescription(GetText(strs.dice_rolled_num(Format.Bold(values.Count.ToString()))))
                .WithImageUrl($"attachment://{imageName}");

            await ctx.Channel.SendFileAsync(ms,
                imageName,
                embed: eb.Build());
        }

        private async Task InternallDndRoll(string arg, bool ordered)
        {
            Match match;
            if ((match = _fudgeRegex.Match(arg)).Length != 0
                && int.TryParse(match.Groups["n1"].ToString(), out var n1)
                && n1 is > 0 and < 500)
            {
                var rng = new EllieRandom();

                var rolls = new List<char>();

                for (var i = 0; i < n1; i++)
                    rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
                var embed = _sender.CreateEmbed()
                               .WithOkColor()
                               .WithAuthor(ctx.User)
                               .WithDescription(GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
                               .AddField(Format.Bold("Result"),
                                   string.Join(" ", rolls.Select(c => Format.Code($"[{c}]"))));

                await Response().Embed(embed).SendAsync();
            }
            else if ((match = _dndRegex.Match(arg)).Length != 0)
            {
                var rng = new EllieRandom();
                if (int.TryParse(match.Groups["n1"].ToString(), out n1)
                    && int.TryParse(match.Groups["n2"].ToString(), out var n2)
                    && n1 <= 50
                    && n2 <= 100000
                    && n1 > 0
                    && n2 > 0)
                {
                    if (!int.TryParse(match.Groups["add"].Value, out var add))
                        add = 0;
                    if (!int.TryParse(match.Groups["sub"].Value, out var sub))
                        sub = 0;

                    var arr = new int[n1];
                    for (var i = 0; i < n1; i++)
                        arr[i] = rng.Next(1, n2 + 1);

                    var sum = arr.Sum();
                    var embed = _sender.CreateEmbed()
                                   .WithOkColor()
                                   .WithAuthor(ctx.User)
                                   .WithDescription(GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
                                   .AddField(Format.Bold(GetText(strs.rolls)),
                                       string.Join(" ",
                                           (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x
                                               => Format.Code(x.ToString()))))
                                   .AddField(Format.Bold("Sum"),
                                       sum + " + " + add + " - " + sub + " = " + (sum + add - sub));
                    await Response().Embed(embed).SendAsync();
                }
            }
        }

        [Cmd]
        public async Task NRoll([Leftover] string range)
        {
            int rolled;
            if (range.Contains("-"))
            {
                var arr = range.Split('-').Take(2).Select(int.Parse).ToArray();
                if (arr[0] > arr[1])
                {
                    await Response().Error(strs.second_larger_than_first).SendAsync();
                    return;
                }

                rolled = new EllieRandom().Next(arr[0], arr[1] + 1);
            }
            else
                rolled = new EllieRandom().Next(0, int.Parse(range) + 1);

            await Response().Confirm(strs.dice_rolled(Format.Bold(rolled.ToString()))).SendAsync();
        }

        private async Task<Image<Rgba32>> GetDiceAsync(int num)
        {
            if (num is < 0 or > 10)
                throw new ArgumentOutOfRangeException(nameof(num));

            if (num == 10)
            {
                using var imgOne = Image.Load<Rgba32>(await _images.GetDiceAsync(1));
                using var imgZero = Image.Load<Rgba32>(await _images.GetDiceAsync(0));
                return new[] { imgOne, imgZero }.Merge();
            }

            return Image.Load<Rgba32>(await _images.GetDiceAsync(num));
        }
    }
}