// Copyright (C) 2021-present sam (starshines.gay)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

using System.ComponentModel;
using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Database.Repositories;
using Catalogger.Backend.Extensions;
using Remora.Commands.Attributes;
using Remora.Commands.Groups;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Objects;
using Remora.Discord.Commands.Attributes;
using Remora.Discord.Commands.Feedback.Services;
using Remora.Discord.Commands.Services;
using Remora.Discord.Pagination.Extensions;
using IResult = Remora.Results.IResult;

namespace Catalogger.Backend.Bot.Commands;

[Group("redirects")]
[Description("Commands for configuring log redirects.")]
[DiscordDefaultMemberPermissions(DiscordPermission.ManageGuild)]
public class RedirectCommands(
    GuildRepository guildRepository,
    GuildCache guildCache,
    ChannelCache channelCache,
    ContextInjectionService contextInjectionService,
    FeedbackService feedbackService
) : CommandGroup
{
    [Command("add")]
    [Description("Redirect message logs from a channel or category to another channel.")]
    public async Task<IResult> AddRedirectAsync(
        [ChannelTypes(
            ChannelType.GuildCategory,
            ChannelType.GuildText,
            ChannelType.GuildAnnouncement,
            ChannelType.GuildForum,
            ChannelType.GuildMedia,
            ChannelType.GuildVoice
        )]
        [Description("The channel to redirect logs from")]
            IChannel source,
        [ChannelTypes(ChannelType.GuildText)]
        [Description("The channel to redirect logs to")]
            IChannel target
    )
    {
        var (_, guildId) = contextInjectionService.GetUserAndGuild();
        var guildConfig = await guildRepository.GetAsync(guildId);
        guildConfig.Channels.Redirects[source.ID.Value] = target.ID.Value;
        await guildRepository.UpdateConfigAsync(guildId, guildConfig);

        var output =
            $"Success! Edited and deleted messages from {FormatChannel(source)} will now be redirected to <#{target.ID}>.";

        // Notify the user if the channel they started redirecting from was already covered by a category-level redirect.
        if (
            source.ParentID.IsDefined(out var parentId)
            && guildConfig.Channels.Redirects.TryGetValue(
                parentId.Value.Value,
                out var parentRedirect
            )
            && parentRedirect != 0
        )
        {
            output +=
                $"\n**Note:** channels from the category {FormatChannel(source)} is in were already being redirected to <#{parentRedirect}>.";
        }

        return await feedbackService.ReplyAsync(output);
    }

    [Command("remove")]
    [Description("Stop redirecting message logs from a channel.")]
    public async Task<IResult> RemoveRedirectAsync(
        [ChannelTypes(
            ChannelType.GuildCategory,
            ChannelType.GuildText,
            ChannelType.GuildAnnouncement,
            ChannelType.GuildForum,
            ChannelType.GuildMedia,
            ChannelType.GuildVoice
        )]
            IChannel source
    )
    {
        var (_, guildId) = contextInjectionService.GetUserAndGuild();
        var guildConfig = await guildRepository.GetAsync(guildId);

        var wasSet = guildConfig.Channels.Redirects.Remove(source.ID.Value);
        await guildRepository.UpdateConfigAsync(guildId, guildConfig);

        var output = wasSet
            ? $"Removed the redirect for {FormatChannel(source)}! Message logs from"
                + $"{(source.Type == ChannelType.GuildCategory ? "that category's channels" : "that channel")}"
                + "will now be logged to the default channel, if any."
            : $"Message logs from {FormatChannel(source)} were already not being redirected to another channel.";

        // Warn the user if this is a non-category channel and *all* of this category's channels are being redirected.
        if (
            source.ParentID.IsDefined(out var parentId)
            && guildConfig.Channels.Redirects.TryGetValue(
                parentId.Value.Value,
                out var parentRedirect
            )
            && parentRedirect != 0
        )
        {
            var parentChannelName = $"<#{parentId}>";
            if (channelCache.TryGet(parentId.Value, out var parentChannel))
                parentChannelName = parentChannel.Name.Value!;

            if (wasSet)
                output +=
                    $"\nHowever, all channels in the {parentChannelName} category are being redirected to <#{parentRedirect}>, "
                    + $"so removing the redirect for {FormatChannel(source)} will not take effect until that is removed as well.";
            else
                output +=
                    $"\nHowever, all channels in the {parentChannelName} category are being redirected to <#{parentRedirect}>.";
        }

        return await feedbackService.ReplyAsync(output);
    }

    [Command("list")]
    [Description("List currently redirected channels.")]
    public async Task<IResult> ListRedirectsAsync()
    {
        var (userId, guildId) = contextInjectionService.GetUserAndGuild();
        if (!guildCache.TryGet(guildId, out var guild))
            throw new CataloggerError("Guild was not cached");
        var guildChannels = channelCache.GuildChannels(guildId).ToList();
        var guildConfig = await guildRepository.GetAsync(guildId);

        var fields = new List<IEmbedField>();

        foreach (var (source, target) in guildConfig.Channels.Redirects)
        {
            fields.Add(
                new EmbedField(
                    Name: FormatChannelHeader(
                        source,
                        guildChannels.FirstOrDefault(c => c.ID.Value == source)
                    ),
                    Value: FormatChannelText(
                        target,
                        guildChannels.FirstOrDefault(c => c.ID.Value == target)
                    ),
                    IsInline: false
                )
            );
        }

        if (fields.Count == 0)
            return await feedbackService.ReplyAsync(
                "No channels are being redirected right now.",
                isEphemeral: true
            );

        return await feedbackService.SendContextualPaginatedMessageAsync(
            userId,
            DiscordUtils.PaginateFields(fields, $"Channel redirects for {guild.Name}")
        );
    }

    private static string FormatChannel(IChannel channel) =>
        channel.Type == ChannelType.GuildCategory ? channel.Name.Value! : $"<#{channel.ID}>";

    private static string FormatChannelHeader(ulong id, IChannel? channel) =>
        channel != null
            ? channel.Type == ChannelType.GuildCategory
                ? $"Category {channel.Name}"
                : $"#{channel.Name}"
            : $"*unknown channel {id}*";

    private static string FormatChannelText(ulong id, IChannel? channel) =>
        channel != null
            ? $"#{channel.Name} (<#{channel.ID}>)"
            : $"*unknown channel {id}* (<#{id}>)";
}
