using LinqToDB;
using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.ModuleBehaviors;
using EllieBot.Db.Models;

namespace EllieBot.Modules.Administration;

public class TempRoleService : IReadyExecutor, IEService
{
    private readonly DbService _db;
    private readonly DiscordSocketClient _client;
    private readonly IBotCreds _creds;

    private TaskCompletionSource<bool> _tcs = new();

    public TempRoleService(
        DbService db,
        DiscordSocketClient client,
        IBotCreds creds)
    {
        _db = db;
        _client = client;
        _creds = creds;
    }

    public async Task AddTempRoleAsync(
        ulong guildId,
        ulong roleId,
        ulong userId,
        TimeSpan duration)
    {
        if (duration == TimeSpan.Zero)
        {
            await using var uow = _db.GetDbContext();
            await uow.GetTable<TempRole>()
                     .Where(x => x.GuildId == guildId && x.UserId == userId)
                     .DeleteAsync();
            return;
        }

        var until = DateTime.UtcNow.Add(duration);
        await using var ctx = _db.GetDbContext();
        await ctx.GetTable<TempRole>()
                 .InsertOrUpdateAsync(() => new()
                 {
                     GuildId = guildId,
                     RoleId = roleId,
                     UserId = userId,
                     Remove = false,
                     ExpiresAt = until
                 },
                     (old) => new()
                     {
                         ExpiresAt = until,
                     },
                     () => new()
                     {
                         GuildId = guildId,
                         UserId = userId,
                         RoleId = roleId
                     });

        _tcs.TrySetResult(true);
    }

    public async Task OnReadyAsync()
    {
        while (true)
        {
            try
            {
                _tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
                var latest = await _db.GetDbContext()
                                      .GetTable<TempRole>()
                                      .Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
                                          _creds.TotalShards,
                                          _client.ShardId))
                                      .OrderBy(x => x.ExpiresAt)
                                      .FirstOrDefaultAsyncLinqToDB();

                if (latest == default)
                {
                    await _tcs.Task;
                    continue;
                }

                var now = DateTime.UtcNow;
                if (latest.ExpiresAt > now)
                {
                    await Task.WhenAny(Task.Delay(latest.ExpiresAt - now), _tcs.Task);
                    continue;
                }

                var deleted = await _db.GetDbContext()
                                       .GetTable<TempRole>()
                                       .Where(x => Linq2DbExpressions.GuildOnShard(x.GuildId,
                                                       _creds.TotalShards,
                                                       _client.ShardId)
                                                   && x.ExpiresAt <= now)
                                       .DeleteWithOutputAsync();

                foreach (var d in deleted)
                {
                    try
                    {
                        await RemoveRole(d);
                    }
                    catch
                    {
                        Log.Warning("Unable to remove temp role {RoleId} from user {UserId}",
                            d.RoleId,
                            d.UserId);
                    }

                    await Task.Delay(1000);
                }
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Unexpected error occurred in temprole loop");
                await Task.Delay(1000);
            }
        }
    }

    private async Task RemoveRole(TempRole tempRole)
    {
        var guild = _client.GetGuild(tempRole.GuildId);

        var role = guild?.GetRole(tempRole.RoleId);
        if (role is null)
            return;

        var user = guild?.GetUser(tempRole.UserId);
        if (user is null)
            return;

        await user.RemoveRoleAsync(role);
    }
}