using System.Collections.Concurrent;
using Catalogger.Backend.Bot;
using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Database.Models;
using Catalogger.Backend.Database.Repositories;
using Catalogger.Backend.Extensions;
using Remora.Discord.API;
using Remora.Discord.Extensions.Embeds;
using Remora.Rest.Core;

namespace Catalogger.Backend.Services;

public class TimeoutService(
    IServiceProvider serviceProvider,
    ILogger logger,
    WebhookExecutorService webhookExecutor,
    UserCache userCache
)
{
    private readonly ILogger _logger = logger.ForContext<TimeoutService>();
    private readonly ConcurrentDictionary<int, Timer> _timers = new();

    public async Task InitializeAsync()
    {
        _logger.Information("Populating timeout service with existing database timeouts");

        await using var scope = serviceProvider.CreateAsyncScope();
        var timeoutRepository = scope.ServiceProvider.GetRequiredService<TimeoutRepository>();

        var timeouts = await timeoutRepository.GetAllAsync();
        foreach (var timeout in timeouts)
            AddTimer(timeout);
    }

    public void AddTimer(DiscordTimeout timeout)
    {
        _logger.Debug("Adding timeout {TimeoutId} to queue", timeout.Id);

        RemoveTimer(timeout.Id);
        _timers[timeout.Id] = new Timer(
            _ =>
            {
                var __ = SendTimeoutLogAsync(timeout.Id);
            },
            null,
            timeout.Until.ToDateTimeOffset() - DateTimeOffset.UtcNow,
            Timeout.InfiniteTimeSpan
        );
    }

    private async Task SendTimeoutLogAsync(int timeoutId)
    {
        _logger.Information("Sending timeout log for {TimeoutId}", timeoutId);

        await using var scope = serviceProvider.CreateAsyncScope();
        var guildRepository = scope.ServiceProvider.GetRequiredService<GuildRepository>();
        var timeoutRepository = scope.ServiceProvider.GetRequiredService<TimeoutRepository>();

        var timeout = await timeoutRepository.RemoveAsync(timeoutId);
        if (timeout == null)
        {
            _logger.Warning("Timeout {TimeoutId} not found, can't log anything", timeoutId);
            return;
        }

        var userId = DiscordSnowflake.New(timeout.UserId);
        var moderatorId =
            timeout.ModeratorId != null
                ? DiscordSnowflake.New(timeout.ModeratorId.Value)
                : (Snowflake?)null;

        var user = await userCache.GetUserAsync(userId);
        if (user == null)
        {
            _logger.Warning("Could not get user {UserId} from cache, can't log timeout", userId);
            return;
        }

        var embed = new EmbedBuilder()
            .WithAuthor(user.Tag(), null, user.AvatarUrl())
            .WithTitle("Member timeout ended")
            .WithDescription($"<@{user.ID}>")
            .WithColour(DiscordUtils.Green)
            .WithFooter($"User ID: {user.ID}")
            .WithCurrentTimestamp();

        if (moderatorId != null)
        {
            var moderator = await userCache.TryFormatUserAsync(moderatorId.Value);
            embed.AddField("Originally timed out by", moderator);
        }
        else
        {
            embed.AddField("Originally timed out by", "*(unknown)*");
        }

        try
        {
            var guildConfig = await guildRepository.GetAsync(DiscordSnowflake.New(timeout.GuildId));
            webhookExecutor.QueueLog(
                guildConfig,
                LogChannelType.GuildMemberTimeout,
                embed.Build().GetOrThrow()
            );
        }
        catch (Exception e)
        {
            _logger.Error(e, "Could not log timeout {TimeoutId} expiring", timeout.Id);
        }
    }

    public void RemoveTimer(int timeoutId)
    {
        if (!_timers.TryRemove(timeoutId, out var timer))
            return;
        _logger.Debug("Removing timeout {TimeoutId} from queue", timeoutId);
        timer.Dispose();
    }
}
