using System.ComponentModel.DataAnnotations;
using System.Text;
using EllieBot.Modules.Games.Fish;
using Format = Discord.Format;

namespace EllieBot.Modules.Games;

public partial class Games
{
    public class FishCommands(
        FishService fs,
        FishConfigService fcs,
        IBotCache cache,
        CaptchaService captchaService) : EllieModule
    {
        private static readonly EllieRandom _rng = new();

        private TypedKey<bool> FishingWhitelistKey(ulong userId)
            => new($"fishingwhitelist:{userId}");

        [Cmd]
        public async Task Fish()
        {
            var cRes = await cache.GetAsync(FishingWhitelistKey(ctx.User.Id));
            if (cRes.TryPickT1(out _, out _))
            {
                string? password = null;
                if (fcs.Data.RequireCaptcha)
                    password = await captchaService.GetUserCaptcha(ctx.User.Id);

                if (password is not null)
                {
                    var img = captchaService.GetPasswordImage(password);
                    using var stream = await img.ToStreamAsync();

                    var toSend = Response()
                        .File(stream, "timely.png");

#if GLOBAL_ELLIE
                    if (_rng.Next(0, 8) == 0)
                        toSend = toSend
                            .Text("*[Sub on Patreon](https://patreon.com/elliebot) to remove captcha.*");
#endif
                    var captcha = await toSend.SendAsync();

                    try
                    {
                        var userInput = await GetUserInputAsync(ctx.User.Id, ctx.Channel.Id);
                        if (userInput?.ToLowerInvariant() != password?.ToLowerInvariant())
                        {
                            return;
                        }

                        // whitelist the user for 30 minutes
                        await cache.AddAsync(FishingWhitelistKey(ctx.User.Id), true, TimeSpan.FromMinutes(30));
                        // reset the password
                        await captchaService.ClearUserCaptcha(ctx.User.Id);
                    }
                    finally
                    {
                        _ = captcha.DeleteAsync();
                    }
                }
            }


            var fishResult = await fs.FishAsync(ctx.User.Id, ctx.Channel.Id);
            if (fishResult.TryPickT1(out _, out var fishTask))
            {
                return;
            }

            var currentWeather = fs.GetCurrentWeather();
            var currentTod = fs.GetTime();
            var spot = fs.GetSpot(ctx.Channel.Id);

            var msg = await Response()
                .Embed(CreateEmbed()
                    .WithPendingColor()
                    .WithAuthor(ctx.User)
                    .WithDescription(GetText(strs.fish_waiting))
                    .AddField(GetText(strs.fish_spot), GetSpotEmoji(spot) + " " + spot.ToString(), true)
                    .AddField(GetText(strs.fish_weather),
                        GetWeatherEmoji(currentWeather) + " " + currentWeather,
                        true)
                    .AddField(GetText(strs.fish_tod), GetTodEmoji(currentTod) + " " + currentTod, true))
                .SendAsync();

            var res = await fishTask;
            if (res is null)
            {
                await Response().Error(strs.fish_nothing).SendAsync();
                return;
            }

            var desc = GetText(strs.fish_caught(res.Fish.Emoji + " " + Format.Bold(res.Fish.Name)));

            if (res.IsSkillUp)
            {
                desc += "\n" + GetText(strs.fish_skill_up(res.Skill, res.MaxSkill));
            }

            await Response()
                .Embed(CreateEmbed()
                    .WithOkColor()
                    .WithAuthor(ctx.User)
                    .WithDescription(desc)
                    .AddField(GetText(strs.fish_quality), GetStarText(res.Stars, res.Fish.Stars), true)
                    .AddField(GetText(strs.desc), res.Fish.Fluff, true)
                    .WithThumbnailUrl(res.Fish.Image))
                .SendAsync();

            await msg.DeleteAsync();
        }

        [Cmd]
        public async Task FishSpot()
        {
            var ws = fs.GetWeatherForPeriods(7);
            var spot = fs.GetSpot(ctx.Channel.Id);
            var time = fs.GetTime();

            await Response()
                .Embed(CreateEmbed()
                    .WithOkColor()
                    .WithDescription(GetText(strs.fish_weather_duration(fs.GetWeatherPeriodDuration())))
                    .AddField(GetText(strs.fish_spot), GetSpotEmoji(spot) + " " + spot, true)
                    .AddField(GetText(strs.fish_tod), GetTodEmoji(time) + " " + time, true)
                    .AddField(GetText(strs.fish_weather_forecast),
                        ws.Select(x => GetWeatherEmoji(x)).Join(""),
                        true))
                .SendAsync();
        }

        [Cmd]
        public async Task Fishlist(int page = 1)
        {
            if (--page < 0)
                return;

            var fishes = await fs.GetAllFish();

            var catches = await fs.GetUserCatches(ctx.User.Id);
            var (skill, maxSkill) = await fs.GetSkill(ctx.User.Id);

            var catchDict = catches.ToDictionary(x => x.FishId, x => x);

            await Response()
                .Paginated()
                .Items(fishes)
                .PageSize(9)
                .CurrentPage(page)
                .Page((fs, i) =>
                {
                    var eb = CreateEmbed()
                        .WithDescription($"🧠 **Skill:** {skill} / {maxSkill}")
                        .WithAuthor(ctx.User)
                        .WithTitle(GetText(strs.fish_list_title))
                        .WithOkColor();

                    foreach (var f in fs)
                    {
                        if (catchDict.TryGetValue(f.Id, out var c))
                        {
                            eb.AddField(f.Name,
                                GetFishEmoji(f, c.Count)
                                + " "
                                + GetSpotEmoji(f.Spot)
                                + GetTodEmoji(f.Time)
                                + GetWeatherEmoji(f.Weather)
                                + "\n"
                                + GetStarText(c.MaxStars, f.Stars)
                                + "\n"
                                + Format.Italics(f.Fluff),
                                true);
                        }
                        else
                        {
                            eb.AddField("?", GetFishEmoji(null, 0) + "\n" + GetStarText(0, f.Stars), true);
                        }
                    }

                    return eb;
                })
                .SendAsync();
        }

        private string GetFishEmoji(FishData? fish, int count)
        {
            if (fish is null)
                return "";

            return fish.Emoji + " x" + count;
        }

        private string GetSpotEmoji(FishingSpot? spot)
        {
            if (spot is not FishingSpot fs)
                return string.Empty;

            var conf = fcs.Data;

            return conf.SpotEmojis[(int)fs];
        }

        private string GetTodEmoji(FishingTime? fishTod)
        {
            return fishTod switch
            {
                FishingTime.Night => "🌑",
                FishingTime.Dawn => "🌅",
                FishingTime.Dusk => "🌆",
                FishingTime.Day => "☀️",
                _ => ""
            };
        }

        private string GetWeatherEmoji(FishingWeather? w)
            => w switch
            {
                FishingWeather.Rain => "🌧️",
                FishingWeather.Snow => "❄️",
                FishingWeather.Storm => "⛈️",
                FishingWeather.Clear => "☀️",
                _ => ""
            };

        private string GetStarText(int resStars, int fishStars)
        {
            if (resStars == fishStars)
            {
                return MultiplyStars(fcs.Data.StarEmojis[^1], fishStars);
            }

            var c = fcs.Data;
            var starsp1 = MultiplyStars(c.StarEmojis[resStars], resStars);
            var starsp2 = MultiplyStars(c.StarEmojis[0], fishStars - resStars);

            return starsp1 + starsp2;
        }

        private string MultiplyStars(string starEmoji, int count)
        {
            var sb = new StringBuilder();

            for (var i = 0; i < count; i++)
            {
                sb.Append(starEmoji);
            }

            return sb.ToString();
        }
    }
}

public enum FishingSpot
{
    Ocean,
    River,
    Lake,
    Swamp,
    Reef
}

public enum FishingTime
{
    Night,
    Dawn,
    Day,
    Dusk
}

public enum FishingWeather
{
    Clear,
    Rain,
    Storm,
    Snow
}