// 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.Net;
using Catalogger.Backend.Api.Middleware;
using Catalogger.Backend.Cache;
using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Database;
using Catalogger.Backend.Database.Repositories;
using Catalogger.Backend.Extensions;
using Catalogger.Backend.Services;
using Microsoft.AspNetCore.Mvc;
using Remora.Discord.API;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
using Remora.Rest.Core;

namespace Catalogger.Backend.Api;

[Route("/api/guilds/{id}")]
public partial class GuildsController(
    ILogger logger,
    DatabaseConnection dbConn,
    GuildRepository guildRepository,
    InviteRepository inviteRepository,
    WatchlistRepository watchlistRepository,
    ChannelCache channelCache,
    RoleCache roleCache,
    IMemberCache memberCache,
    IInviteCache inviteCache,
    UserCache userCache,
    DiscordRequestService discordRequestService,
    IDiscordRestUserAPI userApi,
    WebhookExecutorService webhookExecutor
) : ApiControllerBase
{
    private readonly ILogger _logger = logger.ForContext<GuildsController>();

    private async Task<(Snowflake GuildId, Guild Guild)> ParseGuildAsync(string id)
    {
        var guilds = await discordRequestService.GetGuildsAsync(CurrentToken);

        var guild = guilds.FirstOrDefault(g => g.CanManage && g.Id == id);
        if (guild == null)
            throw new ApiError(
                HttpStatusCode.NotFound,
                ErrorCode.UnknownGuild,
                "Unknown server, or you're not in it, or you can't manage it."
            );

        if (!DiscordSnowflake.TryParse(guild.Id, out var guildId))
            throw new CataloggerError("Invalid snowflake passed to GuildsController.ToResponse");

        return (guildId.Value, guild);
    }

    [Authorize]
    [HttpGet]
    public async Task<IActionResult> GetGuildAsync(string id)
    {
        var (guildId, guild) = await ParseGuildAsync(id);

        var guildConfig = await guildRepository.GetAsync(guildId);

        var channels = channelCache
            .GuildChannels(guildId)
            .OrderBy(c => c.Position.OrDefault(0))
            .ToList();

        var channelsWithoutCategories = channels
            .Where(c => !c.ParentID.IsDefined() && c.Type is not ChannelType.GuildCategory)
            .Select(ToChannel);

        var categories = channels
            .Where(c => c.Type is ChannelType.GuildCategory)
            .Select(c => new GuildCategory(
                c.ID.ToString(),
                c.Name.Value!,
                channels
                    .Where(c2 => c2.ParentID.IsDefined(out var parentId) && parentId == c.ID)
                    .Select(ToChannel)
            ));

        var roles = roleCache
            .GuildRoles(guildId)
            .OrderByDescending(r => r.Position)
            .Select(r => new GuildRole(
                r.ID.ToString(),
                r.Name,
                r.Position,
                r.Colour.ToPrettyString()
            ));

        return Ok(
            new GuildResponse(
                guild.Id,
                guild.Name,
                guild.IconUrl,
                categories,
                channelsWithoutCategories,
                roles,
                guildConfig.IgnoredChannels,
                guildConfig.IgnoredRoles,
                guildConfig.Messages,
                guildConfig.Channels,
                guildConfig.KeyRoles
            )
        );
    }

    private static GuildChannel ToChannel(IChannel channel) =>
        new(
            channel.ID.ToString(),
            channel.Name.Value!,
            channel.Type is ChannelType.GuildText,
            channel.Type
                is ChannelType.GuildText
                    or ChannelType.GuildAnnouncement
                    or ChannelType.GuildForum
                    or ChannelType.GuildMedia
                    or ChannelType.GuildVoice
        );

    private record GuildResponse(
        string Id,
        string Name,
        string IconUrl,
        IEnumerable<GuildCategory> Categories,
        IEnumerable<GuildChannel> ChannelsWithoutCategory,
        IEnumerable<GuildRole> Roles,
        List<ulong> IgnoredChannels,
        List<ulong> IgnoredRoles,
        Database.Models.Guild.MessageConfig Messages,
        Database.Models.Guild.ChannelConfig Channels,
        List<ulong> KeyRoles
    );

    private record GuildCategory(string Id, string Name, IEnumerable<GuildChannel> Channels);

    private record GuildChannel(string Id, string Name, bool CanLogTo, bool CanRedirectFrom);

    private record GuildRole(string Id, string Name, int Position, string Colour);

    [Authorize]
    [HttpPatch]
    [ProducesResponseType<Database.Models.Guild.ChannelConfig>(statusCode: StatusCodes.Status200OK)]
    public async Task<IActionResult> PatchGuildAsync(string id, [FromBody] ChannelRequest req)
    {
        var (guildId, _) = await ParseGuildAsync(id);
        var guildChannels = channelCache
            .GuildChannels(guildId)
            .Where(c => c.Type is ChannelType.GuildText)
            .ToList();
        var guildConfig = await guildRepository.GetAsync(guildId);

        // i love repeating myself wheeeeee
        if (
            req.GuildUpdate == null
            || (req.GuildUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.GuildUpdate))
        )
            guildConfig.Channels.GuildUpdate = req.GuildUpdate ?? 0;
        if (
            req.GuildEmojisUpdate == null
            || (
                req.GuildEmojisUpdate == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildEmojisUpdate)
            )
        )
            guildConfig.Channels.GuildEmojisUpdate = req.GuildEmojisUpdate ?? 0;
        if (
            req.GuildRoleCreate == null
            || (
                req.GuildRoleCreate == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildRoleCreate)
            )
        )
            guildConfig.Channels.GuildRoleCreate = req.GuildRoleCreate ?? 0;
        if (
            req.GuildRoleUpdate == null
            || (
                req.GuildRoleUpdate == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildRoleUpdate)
            )
        )
            guildConfig.Channels.GuildRoleUpdate = req.GuildRoleUpdate ?? 0;
        if (
            req.GuildRoleDelete == null
            || (
                req.GuildRoleDelete == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildRoleDelete)
            )
        )
            guildConfig.Channels.GuildRoleDelete = req.GuildRoleDelete ?? 0;
        if (
            req.ChannelCreate == null
            || (req.ChannelCreate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelCreate))
        )
            guildConfig.Channels.ChannelCreate = req.ChannelCreate ?? 0;
        if (
            req.ChannelUpdate == null
            || (req.ChannelUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelUpdate))
        )
            guildConfig.Channels.ChannelUpdate = req.ChannelUpdate ?? 0;
        if (
            req.ChannelDelete == null
            || (req.ChannelDelete == 0 || guildChannels.Any(c => c.ID.Value == req.ChannelDelete))
        )
            guildConfig.Channels.ChannelDelete = req.ChannelDelete ?? 0;
        if (
            req.GuildMemberAdd == null
            || (req.GuildMemberAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildMemberAdd))
        )
            guildConfig.Channels.GuildMemberAdd = req.GuildMemberAdd ?? 0;
        if (
            req.GuildMemberUpdate == null
            || (
                req.GuildMemberUpdate == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildMemberUpdate)
            )
        )
            guildConfig.Channels.GuildMemberUpdate = req.GuildMemberUpdate ?? 0;
        if (
            req.GuildKeyRoleUpdate == null
            || (
                req.GuildKeyRoleUpdate == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildKeyRoleUpdate)
            )
        )
            guildConfig.Channels.GuildKeyRoleUpdate = req.GuildKeyRoleUpdate ?? 0;
        if (
            req.GuildMemberNickUpdate == null
            || (
                req.GuildMemberNickUpdate == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildMemberNickUpdate)
            )
        )
            guildConfig.Channels.GuildMemberNickUpdate = req.GuildMemberNickUpdate ?? 0;
        if (
            req.GuildMemberAvatarUpdate == null
            || (
                req.GuildMemberAvatarUpdate == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildMemberAvatarUpdate)
            )
        )
            guildConfig.Channels.GuildMemberAvatarUpdate = req.GuildMemberAvatarUpdate ?? 0;
        if (
            req.GuildMemberTimeout == null
            || (
                req.GuildMemberTimeout == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildMemberTimeout)
            )
        )
            guildConfig.Channels.GuildMemberTimeout = req.GuildMemberTimeout ?? 0;
        if (
            req.GuildMemberRemove == null
            || (
                req.GuildMemberRemove == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildMemberRemove)
            )
        )
            guildConfig.Channels.GuildMemberRemove = req.GuildMemberRemove ?? 0;
        if (
            req.GuildMemberKick == null
            || (
                req.GuildMemberKick == 0
                || guildChannels.Any(c => c.ID.Value == req.GuildMemberKick)
            )
        )
            guildConfig.Channels.GuildMemberKick = req.GuildMemberKick ?? 0;
        if (
            req.GuildBanAdd == null
            || (req.GuildBanAdd == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanAdd))
        )
            guildConfig.Channels.GuildBanAdd = req.GuildBanAdd ?? 0;
        if (
            req.GuildBanRemove == null
            || (req.GuildBanRemove == 0 || guildChannels.Any(c => c.ID.Value == req.GuildBanRemove))
        )
            guildConfig.Channels.GuildBanRemove = req.GuildBanRemove ?? 0;
        if (
            req.InviteCreate == null
            || (req.InviteCreate == 0 || guildChannels.Any(c => c.ID.Value == req.InviteCreate))
        )
            guildConfig.Channels.InviteCreate = req.InviteCreate ?? 0;
        if (
            req.InviteDelete == null
            || (req.InviteDelete == 0 || guildChannels.Any(c => c.ID.Value == req.InviteDelete))
        )
            guildConfig.Channels.InviteDelete = req.InviteDelete ?? 0;
        if (
            req.MessageUpdate == null
            || (req.MessageUpdate == 0 || guildChannels.Any(c => c.ID.Value == req.MessageUpdate))
        )
            guildConfig.Channels.MessageUpdate = req.MessageUpdate ?? 0;
        if (
            req.MessageDelete == null
            || (req.MessageDelete == 0 || guildChannels.Any(c => c.ID.Value == req.MessageDelete))
        )
            guildConfig.Channels.MessageDelete = req.MessageDelete ?? 0;
        if (
            req.MessageDeleteBulk == null
            || (
                req.MessageDeleteBulk == 0
                || guildChannels.Any(c => c.ID.Value == req.MessageDeleteBulk)
            )
        )
            guildConfig.Channels.MessageDeleteBulk = req.MessageDeleteBulk ?? 0;

        await guildRepository.UpdateConfigAsync(guildId, guildConfig);
        return Ok(guildConfig.Channels);
    }

    public record ChannelRequest(
        ulong? GuildUpdate = null,
        ulong? GuildEmojisUpdate = null,
        ulong? GuildRoleCreate = null,
        ulong? GuildRoleUpdate = null,
        ulong? GuildRoleDelete = null,
        ulong? ChannelCreate = null,
        ulong? ChannelUpdate = null,
        ulong? ChannelDelete = null,
        ulong? GuildMemberAdd = null,
        ulong? GuildMemberUpdate = null,
        ulong? GuildKeyRoleUpdate = null,
        ulong? GuildMemberNickUpdate = null,
        ulong? GuildMemberAvatarUpdate = null,
        ulong? GuildMemberTimeout = null,
        ulong? GuildMemberRemove = null,
        ulong? GuildMemberKick = null,
        ulong? GuildBanAdd = null,
        ulong? GuildBanRemove = null,
        ulong? InviteCreate = null,
        ulong? InviteDelete = null,
        ulong? MessageUpdate = null,
        ulong? MessageDelete = null,
        ulong? MessageDeleteBulk = null
    );
}
