// 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 Catalogger.Backend.Database.Redis;
using Remora.Discord.API;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Objects;
using Remora.Rest.Core;

namespace Catalogger.Backend.Cache.RedisCache;

public class RedisInviteCache(RedisService redisService) : IInviteCache
{
    public async Task<IEnumerable<IInviteWithMetadata>> TryGetAsync(Snowflake guildId)
    {
        var redisInvites =
            await redisService.GetAsync<List<RedisInvite>>(InvitesKey(guildId)) ?? [];
        return redisInvites.Select(r => r.ToRemoraInvite());
    }

    public async Task SetAsync(Snowflake guildId, IEnumerable<IInviteWithMetadata> invites) =>
        await redisService.SetAsync(InvitesKey(guildId), invites.Select(RedisInvite.FromIInvite));

    public async Task RemoveAsync(Snowflake guildId) =>
        await redisService.GetDatabase().KeyDeleteAsync(InvitesKey(guildId));

    private static string InvitesKey(Snowflake guildId) => $"guild-invites:{guildId}";
}

internal record RedisInvite(
    string Code,
    RedisPartialGuild? Guild,
    RedisPartialChannel? Channel,
    int Uses,
    int MaxUses,
    TimeSpan MaxAge,
    bool IsTemporary,
    DateTimeOffset CreatedAt,
    RedisUser? Inviter,
    DateTimeOffset? ExpiresAt
)
{
    public static RedisInvite FromIInvite(IInviteWithMetadata invite) =>
        new(
            invite.Code,
            invite.Guild.Map(RedisPartialGuild.FromIPartialGuild).OrDefault(),
            invite.Channel != null ? RedisPartialChannel.FromIPartialChannel(invite.Channel) : null,
            invite.Uses,
            invite.MaxUses,
            invite.MaxAge,
            invite.IsTemporary,
            invite.CreatedAt,
            invite.Inviter.Map(RedisUser.FromIUser).OrDefault(),
            invite.ExpiresAt.OrDefault()
        );

    public InviteWithMetadata ToRemoraInvite() =>
        new(
            Code,
            Guild?.ToRemoraPartialGuild() ?? new Optional<IPartialGuild>(),
            Channel?.ToRemoraPartialChannel(),
            Uses,
            MaxUses,
            MaxAge,
            IsTemporary,
            CreatedAt,
            Inviter?.ToRemoraUser() ?? new Optional<IUser>(),
            ExpiresAt: ExpiresAt
        );
}

internal record RedisPartialGuild(ulong Id, string? Name)
{
    public static RedisPartialGuild FromIPartialGuild(IPartialGuild guild) =>
        new(guild.ID.Value.Value, guild.Name.OrDefault(null));

    public PartialGuild ToRemoraPartialGuild() =>
        new(DiscordSnowflake.New(Id), Name ?? new Optional<string>());
}

internal record RedisPartialChannel(ulong Id, string? Name)
{
    public static RedisPartialChannel FromIPartialChannel(IPartialChannel channel) =>
        new(channel.ID.Value.Value, channel.Name.OrDefault(null));

    public PartialChannel ToRemoraPartialChannel() => new(DiscordSnowflake.New(Id), Name: Name);
}
