using CompatApiClient.Utils;
using CompatBot.Commands.AutoCompleteProviders;
using CompatBot.Database;
using Microsoft.EntityFrameworkCore;

namespace CompatBot.Commands;

[Command("warning"), RequiresBotModRole, AllowDMUsage]
[Description("Command used to manage warnings")]
internal static partial class Warnings
{
    [Command("give")]
    [Description("Issue a new warning to a user")]
    public static async ValueTask Warn(
        SlashCommandContext ctx,
        [Description("User to warn")]
        DiscordUser user,
        [Description("Warning explanation")]
        string reason
    )
    {
        await ctx.DeferResponseAsync(ephemeral: true).ConfigureAwait(false);
        var (saved, suppress, recent, total) = await AddAsync(user.Id, ctx.User, reason).ConfigureAwait(false);
        if (!saved)
        {
            await ctx.RespondAsync($"{Config.Reactions.Failure} Couldn't save the warning, please try again", ephemeral: true).ConfigureAwait(false);
            return;
        }

        if (!suppress)
        {
            var userMsgContent = await GetDefaultWarningMessageAsync(ctx.Client, user, reason, recent, total, ctx.User).ConfigureAwait(false);
            var userMsg = new DiscordMessageBuilder()
                .WithContent(userMsgContent)
                .AddMention(new UserMention(user));
            await ctx.Channel.SendMessageAsync(userMsg).ConfigureAwait(false);
        }
        await ListUserWarningsAsync(ctx.Client, ctx.Interaction, user.Id, user.Username.Sanitize()).ConfigureAwait(false);
    }

    [Command("update")]
    [Description("Change warning details")]
    public static async ValueTask Edit(
        SlashCommandContext ctx,
        [Description("Warning ID to edit"), SlashAutoCompleteProvider<WarningAutoCompleteProvider>]
        int id,
        [Description("Updated warning explanation")]
        string reason,
        [Description("User to filter autocomplete results")]
        DiscordUser? user = null
    )
    {
        await using var wdb = await BotDb.OpenWriteAsync().ConfigureAwait(false);
        var warnings = await wdb.Warning.Where(w => id.Equals(w.Id)).ToListAsync().ConfigureAwait(false);
        if (warnings.Count is 0)
        {
            await ctx.RespondAsync($"{Config.Reactions.Failure} Warning not found", ephemeral: true).ConfigureAwait(false);
            return;
        }

        var warningToEdit = warnings.First();
        if (warningToEdit.IssuerId != ctx.User.Id)
        {
            await ctx.RespondAsync($"{Config.Reactions.Denied} This warning wasn't issued by you", ephemeral: true).ConfigureAwait(false);
            return;
        }

        warningToEdit.Reason = reason;
        await wdb.SaveChangesAsync().ConfigureAwait(false);
        await ctx.RespondAsync("Warning successfully updated", ephemeral: true).ConfigureAwait(false);
    }

    [Command("remove")]
    [Description("Removes specified warnings")]
    public static async ValueTask Remove(
        SlashCommandContext ctx,
        [Description("Warning ID to remove"), SlashAutoCompleteProvider<WarningAutoCompleteProvider>]
        int id,
        [Description("Reason for warning removal")]
        string reason,
        [Description("User to filter autocomplete results")]
        DiscordUser? user = null
    )
    {
        await ctx.DeferResponseAsync(ephemeral: true).ConfigureAwait(false);
        var removedCount = 0;
        var warnedUserId = 0ul;
        await using (var wdb = await BotDb.OpenWriteAsync().ConfigureAwait(false))
        {
            var warningsToRemove = await wdb.Warning.Where(w => w.Id == id).ToListAsync().ConfigureAwait(false);
            if (warningsToRemove.Count > 0)
            {
                foreach (var w in warningsToRemove)
                {
                    w.Retracted = true;
                    w.RetractedBy = ctx.User.Id;
                    w.RetractionReason = reason;
                    w.RetractionTimestamp = DateTime.UtcNow.Ticks;
                }
                removedCount = await wdb.SaveChangesAsync().ConfigureAwait(false);
                warnedUserId = warningsToRemove[0].DiscordId;
            }
        }
        if (removedCount is 0)
            await ctx.RespondAsync($"{Config.Reactions.Failure} Failed to remove warning", ephemeral: true).ConfigureAwait(false);
        else
        {
            user ??= await ctx.Client.GetUserAsync(warnedUserId).ConfigureAwait(false);
            await ctx.Channel.SendMessageAsync($"Warning successfully removed for {user.Mention} by {ctx.User.Mention}").ConfigureAwait(false);
            await ListUserWarningsAsync(ctx.Client, ctx.Interaction, user.Id, user.Username.Sanitize(), false).ConfigureAwait(false);
        }
    }

    [Command("clear")]
    [Description("Removes **all** warnings for a user")]
    public static async ValueTask Clear(
        SlashCommandContext ctx,
        [Description("User to clear warnings for")]
        DiscordUser user,
        [Description("Reason for clear warning removal")]
        string reason
    )
    {
        try
        {
            await ctx.DeferResponseAsync(ephemeral: true).ConfigureAwait(false);
            int removed;
            await using (var wdb = await BotDb.OpenWriteAsync().ConfigureAwait(false))
            {
                var warningsToRemove = await wdb.Warning.Where(w => w.DiscordId == user.Id && !w.Retracted)
                    .ToListAsync().ConfigureAwait(false);
                foreach (var w in warningsToRemove)
                {
                    w.Retracted = true;
                    w.RetractedBy = ctx.User.Id;
                    w.RetractionReason = reason;
                    w.RetractionTimestamp = DateTime.UtcNow.Ticks;
                }
                removed = await wdb.SaveChangesAsync().ConfigureAwait(false);
            }
            await ctx.Channel.SendMessageAsync($"{removed} warning{StringUtils.GetSuffix(removed)} successfully removed!").ConfigureAwait(false);
            await ListUserWarningsAsync(ctx.Client, ctx.Interaction, user.Id, user.Username.Sanitize()).ConfigureAwait(false);
        }
        catch (Exception e)
        {
            Config.Log.Error(e);
        }
    }

    [Command("revert")]
    [Description("Bring back warning that's been removed before")]
    public static async ValueTask Revert(
        SlashCommandContext ctx,
        [Description("Warning ID to change"), SlashAutoCompleteProvider<WarningAutoCompleteProvider>]
        int id,
        [Description("User to filter autocomplete results")]
        DiscordUser? user = null
    )
    {
        await using var wdb = await BotDb.OpenWriteAsync().ConfigureAwait(false);
        var warn = await wdb.Warning.FirstOrDefaultAsync(w => w.Id == id).ConfigureAwait(false);
        if (warn is { Retracted: true })
        {
            warn.Retracted = false;
            warn.RetractedBy = null;
            warn.RetractionReason = null;
            warn.RetractionTimestamp = null;
            await wdb.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
            await ctx.RespondAsync($"{Config.Reactions.Success} Reissued the warning", ephemeral: true).ConfigureAwait(false);
        }
        else
            await ctx.RespondAsync($"{Config.Reactions.Failure} Warning is not retracted", ephemeral: true).ConfigureAwait(false);
    }

    internal static async ValueTask<string> GetDefaultWarningMessageAsync(
        DiscordClient client,
        DiscordUser userToWarn,
        string reason,
        int recentWarnCount,
        int totalWarnCount,
        DiscordUser moderator)
    {
        var rulesCh = await client.GetChannelAsync(Config.BotRulesChannelId).ConfigureAwait(false);
        return $"""
                ## ⚠️ {userToWarn.Mention} you have been warned ⚠️ 
                **Reason:** {reason}

                **Read the {rulesCh.Mention} before continuing to chat**
                Refusing to read/follow the server rules *will* result in a server ban

                You have {recentWarnCount} recent warning{StringUtils.GetSuffix(recentWarnCount)} ({totalWarnCount} total)
                -# Warning added by {moderator.Mention}
                """;
    }

    internal static async ValueTask<(bool saved, bool suppress, int recentCount, int totalCount)>
        AddAsync(ulong userId, DiscordUser issuer, string reason, string? fullReason = null)
    {
        try
        {
            await using var wdb = await BotDb.OpenWriteAsync().ConfigureAwait(false);
            await wdb.Warning.AddAsync(
                new()
                {
                    DiscordId = userId,
                    IssuerId = issuer.Id,
                    Reason = reason,
                    FullReason = fullReason ?? "",
                    Timestamp = DateTime.UtcNow.Ticks
                }
            ).ConfigureAwait(false);
            await wdb.SaveChangesAsync().ConfigureAwait(false);

            var threshold = DateTime.UtcNow.AddMinutes(-15).Ticks;
            var totalCount = wdb.Warning.Count(w => w.DiscordId == userId && !w.Retracted);
            var recentCount = wdb.Warning.Count(w => w.DiscordId == userId && !w.Retracted && w.Timestamp > threshold);
            if (recentCount < 4)
                return (true, false, recentCount, totalCount);
            
            Config.Log.Debug("Suicide behavior detected, not spamming with warning responses");
            return (true, true, recentCount, totalCount);
        }
        catch (Exception e)
        {
            Config.Log.Error(e, "Couldn't save the warning");
            return default;
        }
    }

    //note: be sure to pass a sanitized userName
    //note2: itneraction must be deferred
    internal static async ValueTask ListUserWarningsAsync(DiscordClient client, DiscordInteraction interaction, ulong userId, string userName, bool skipIfOne = true, bool useFollowup = false)
    {
        try
        {
            var isMod = await interaction.User.IsWhitelistedAsync(client, interaction.Guild).ConfigureAwait(false);
            if (interaction.User.Id != userId && !isMod)
            {
                Config.Log.Error($"Somehow {interaction.User.Username} ({interaction.User.Id}) triggered warning list for {userId}");
                return;
            }

            const bool ephemeral = true;
            int count, removed;
            bool isKot, isDoggo;
            await using var db = await BotDb.OpenReadAsync().ConfigureAwait(false);
            count = await db.Warning.CountAsync(w => w.DiscordId == userId && !w.Retracted).ConfigureAwait(false);
            removed = await db.Warning.CountAsync(w => w.DiscordId == userId && w.Retracted).ConfigureAwait(false);
            isKot = db.Kot.Any(k => k.UserId == userId);
            isDoggo = db.Doggo.Any(d => d.UserId == userId);
            var response = new DiscordInteractionResponseBuilder().AsEphemeral(ephemeral);
            if (count is 0)
            {
                if (isKot && isDoggo)
                {
                    if (new Random().NextDouble() < 0.5)
                        isKot = false;
                    else
                        isDoggo = false;
                }
                var msg = (removed, ephemeral, isKot, isDoggo) switch
                {
                    (0,    _,  true, false) => $"{userName} has no warnings, is an upstanding kot, and a paw bean of this community",
                    (0,    _, false,  true) => $"{userName} has no warnings, is a good boy, and a wiggling tail of this community",
                    (0,    _,     _,     _) => $"{userName} has no warnings, is an upstanding citizen, and a pillar of this community",
                    (_, true,     _,     _) => $"{userName} has no warnings ({removed} retracted warning{(removed == 1 ? "" : "s")})",
                    (_,    _,  true, false) => $"{userName} has no warnings, but are they a good kot?",
                    (_,    _, false,  true) => $"{userName} has no warnings, but are they a good boy?",
                    _ => $"{userName} has no warnings",
                };
                ;
                await interaction.EditOriginalResponseAsync(new(response.WithContent(msg))).ConfigureAwait(false);
                if (!ephemeral || removed is 0)
                    return;
            }

            if (count is 1 && skipIfOne)
            {
                await interaction.EditOriginalResponseAsync(new(response.WithContent("No additional warnings on record"))).ConfigureAwait(false);
                return;
            }

            const int maxWarningsInPublicChannel = 3;
            var showCount = Math.Min(maxWarningsInPublicChannel, count);
            var table = new AsciiTable(
                new AsciiColumn("ID", alignToRight: true),
                new AsciiColumn("±", disabled: !ephemeral || !isMod),
                new AsciiColumn("By", maxWidth: 15),
                new AsciiColumn("On date (UTC)"),
                new AsciiColumn("Reason"),
                new AsciiColumn("Context", disabled: !ephemeral || !isMod, maxWidth: 4096)
            );
            IQueryable<Warning> query = db.Warning.Where(w => w.DiscordId == userId).OrderByDescending(w => w.Id);
            if (!ephemeral || !isMod)
                query = query.Where(w => !w.Retracted);
            if (!ephemeral && !isMod)
                query = query.Take(maxWarningsInPublicChannel);
            foreach (var warning in await query.ToListAsync().ConfigureAwait(false))
            {
                if (warning.Retracted)
                {
                    if (isMod && ephemeral)
                    {
                        var retractedByName = warning.RetractedBy.HasValue
                            ? await client.GetUserNameAsync(interaction.Channel, warning.RetractedBy.Value, ephemeral, "unknown mod").ConfigureAwait(false)
                            : "";
                        var retractionTimestamp = warning.RetractionTimestamp.HasValue
                            ? new DateTime(warning.RetractionTimestamp.Value, DateTimeKind.Utc).ToString("u")
                            : "";
                        table.Add(warning.Id.ToString(), "-", retractedByName, retractionTimestamp, warning.RetractionReason ?? "", "");

                        var issuerName = warning.IssuerId is 0
                            ? ""
                            : await client.GetUserNameAsync(interaction.Channel, warning.IssuerId, ephemeral, "unknown mod").ConfigureAwait(false);
                        var timestamp = warning.Timestamp.HasValue
                            ? new DateTime(warning.Timestamp.Value, DateTimeKind.Utc).ToString("u")
                            : "";
                        table.Add(warning.Id.ToString().StrikeThrough(), "+", issuerName.StrikeThrough(), timestamp.StrikeThrough(), warning.Reason.StrikeThrough(), warning.FullReason.StrikeThrough());
                    }
                }
                else
                {
                    var issuerName = warning.IssuerId is 0
                        ? ""
                        : await client.GetUserNameAsync(interaction.Channel, warning.IssuerId, ephemeral, "unknown mod").ConfigureAwait(false);
                    var timestamp = warning.Timestamp.HasValue
                        ? new DateTime(warning.Timestamp.Value, DateTimeKind.Utc).ToString("u")
                        : "";
                    table.Add(warning.Id.ToString(), "+", issuerName, timestamp, warning.Reason, warning.FullReason);
                }
            }
            
            var result = new StringBuilder("Warning list for ").Append(Formatter.Sanitize(userName));
            if (!ephemeral && !isMod && count > maxWarningsInPublicChannel)
                result.Append($" (last {showCount} of {count}, full list in DMs)");
            result.AppendLine(":").Append(table);
            var pages = AutosplitResponseHelper.AutosplitMessage(result.ToString());
            await interaction.EditOriginalResponseAsync(new(response.WithContent(pages[0]))).ConfigureAwait(false);
            if (useFollowup)
            {
                foreach (var page in pages.Skip(1).Take(EmbedPager.MaxFollowupMessages))
                {
                    var followupMsg = new DiscordFollowupMessageBuilder()
                        .AsEphemeral(ephemeral)
                        .WithContent(page);
                    await interaction.CreateFollowupMessageAsync(followupMsg).ConfigureAwait(false);
                }
            }        }
        catch (Exception e)
        {
            Config.Log.Warn(e);
        }
    }
}