#nullable disable
using LinqToDB.EntityFrameworkCore;
using EllieBot.Common.ModuleBehaviors;
using EllieBot.Db.Models;
using EllieBot.Modules.Administration._common.results;

namespace EllieBot.Modules.Administration;

public class AdministrationService : IEService, IReadyExecutor
{
    private ConcurrentHashSet<ulong> _deleteMessagesOnCommand;
    private ConcurrentDictionary<ulong, bool> _delMsgOnCmdChannels;

    private readonly DbService _db;
    private readonly IReplacementService _repSvc;
    private readonly ILogCommandService _logService;
    private readonly IHttpClientFactory _httpFactory;
    private readonly ShardData _shardData;
    private readonly CommandHandler _cmdHandler;

    public AdministrationService(
        DbService db,
        IReplacementService repSvc,
        ILogCommandService logService,
        IHttpClientFactory factory,
        ShardData shardData,
        CommandHandler cmdHandler)
    {
        _db = db;
        _shardData = shardData;
        _repSvc = repSvc;
        _logService = logService;
        _httpFactory = factory;
        _cmdHandler = cmdHandler;
    }

    public async Task OnReadyAsync()
    {
        await using var uow = _db.GetDbContext();
        _deleteMessagesOnCommand = new(await uow.GetTable<GuildConfig>()
            .Where(x => Queries.GuildOnShard(x.GuildId, _shardData.TotalShards, _shardData.ShardId) &&
                        x.DeleteMessageOnCommand)
            .Select(x => x.GuildId)
            .ToListAsyncLinqToDB());

        _delMsgOnCmdChannels = (await uow.GetTable<DelMsgOnCmdChannel>()
                .Where(x => _deleteMessagesOnCommand.Contains(x.GuildId))
                .ToDictionaryAsyncLinqToDB(x => x.ChannelId, x => x.State))
            .ToConcurrent();

        _cmdHandler.CommandExecuted += DelMsgOnCmd_Handler;
    }

    public async Task<(bool DelMsgOnCmd, IEnumerable<DelMsgOnCmdChannel> channels)> GetDelMsgOnCmdData(ulong guildId)
    {
        await using var uow = _db.GetDbContext();

        var conf = await uow.GetTable<GuildConfig>()
            .Where(x => x.GuildId == guildId)
            .Select(x => x.DeleteMessageOnCommand)
            .FirstOrDefaultAsyncLinqToDB();

        var channels = await uow.GetTable<DelMsgOnCmdChannel>()
            .Where(x => x.GuildId == guildId)
            .ToListAsyncLinqToDB();

        return (conf, channels);
    }

    private Task DelMsgOnCmd_Handler(IUserMessage msg, CommandInfo cmd)
    {
        if (msg.Channel is not ITextChannel channel)
            return Task.CompletedTask;

        _ = Task.Run(async () =>
        {
            //wat ?!
            if (_delMsgOnCmdChannels.TryGetValue(channel.Id, out var state))
            {
                if (state && cmd.Name != "prune" && cmd.Name != "pick")
                {
                    _logService.AddDeleteIgnore(msg.Id);
                    try
                    {
                        await msg.DeleteAsync();
                    }
                    catch
                    {
                    }
                }
                //if state is false, that means do not do it
            }
            else if (_deleteMessagesOnCommand.Contains(channel.Guild.Id) && cmd.Name != "prune" && cmd.Name != "pick")
            {
                _logService.AddDeleteIgnore(msg.Id);
                try
                {
                    await msg.DeleteAsync();
                }
                catch
                {
                }
            }
        });
        return Task.CompletedTask;
    }

    public async Task<bool> ToggleDelMsgOnCmd(ulong guildId)
    {
        await using var uow = _db.GetDbContext();

        var gc = uow.GuildConfigsForId(guildId);
        gc.DeleteMessageOnCommand = !gc.DeleteMessageOnCommand;

        if (gc.DeleteMessageOnCommand)
            _deleteMessagesOnCommand.Add(guildId);
        else
            _deleteMessagesOnCommand.TryRemove(guildId);

        await uow.SaveChangesAsync();
        return gc.DeleteMessageOnCommand;
    }

    public async Task SetDelMsgOnCmdState(ulong guildId, ulong chId, Administration.State newState)
    {
        await using (var uow = _db.GetDbContext())
        {
            var old = await uow.GetTable<DelMsgOnCmdChannel>()
                .Where(x => x.GuildId == guildId && x.ChannelId == chId)
                .FirstOrDefaultAsyncLinqToDB();

            if (newState == Administration.State.Inherit)
            {
                if (old is not null)
                {
                    uow.Remove(old);
                }
            }
            else
            {
                if (old is null)
                {
                    old = new DelMsgOnCmdChannel
                    {
                        GuildId = guildId,
                        ChannelId = chId,
                        State = newState == Administration.State.Enable
                    };
                    uow.Add(old);
                }

                old.State = newState == Administration.State.Enable;
                _delMsgOnCmdChannels[chId] = newState == Administration.State.Enable;
            }

            await uow.SaveChangesAsync();
        }

        if (newState == Administration.State.Disable)
        {
        }
        else if (newState == Administration.State.Enable)
        {
            _delMsgOnCmdChannels[chId] = true;
        }
        else
        {
            _delMsgOnCmdChannels.TryRemove(chId, out _);
        }
    }

    public async Task DeafenUsers(bool value, params IGuildUser[] users)
    {
        if (!users.Any())
            return;
        foreach (var u in users)
        {
            try
            {
                await u.ModifyAsync(usr => usr.Deaf = value);
            }
            catch
            {
                // ignored
            }
        }
    }

    public async Task EditMessage(
        ICommandContext context,
        ITextChannel chanl,
        ulong messageId,
        string input)
    {
        var msg = await chanl.GetMessageAsync(messageId);

        if (msg is not IUserMessage umsg || msg.Author.Id != context.Client.CurrentUser.Id)
            return;

        var repCtx = new ReplacementContext(context);

        var text = SmartText.CreateFrom(input);
        text = await _repSvc.ReplaceAsync(text, repCtx);

        await umsg.EditAsync(text);
    }

    public async Task<SetServerBannerResult> SetServerBannerAsync(IGuild guild, string img)
    {
        if (!IsValidUri(img))
            return SetServerBannerResult.InvalidURL;

        var uri = new Uri(img);

        using var http = _httpFactory.CreateClient();
        using var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);

        if (!sr.IsImage())
            return SetServerBannerResult.InvalidFileType;

        if (sr.GetContentLength() > 8.Megabytes())
        {
            return SetServerBannerResult.Toolarge;
        }

        await using var imageStream = await sr.Content.ReadAsStreamAsync();

        await guild.ModifyAsync(x => x.Banner = new Image(imageStream));
        return SetServerBannerResult.Success;
    }

    public async Task<SetServerIconResult> SetServerIconAsync(IGuild guild, string img)
    {
        if (!IsValidUri(img))
            return SetServerIconResult.InvalidURL;

        var uri = new Uri(img);

        using var http = _httpFactory.CreateClient();
        using var sr = await http.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);

        if (!sr.IsImage())
            return SetServerIconResult.InvalidFileType;

        await using var imageStream = await sr.Content.ReadAsStreamAsync();

        await guild.ModifyAsync(x => x.Icon = new Image(imageStream));
        return SetServerIconResult.Success;
    }

    private bool IsValidUri(string img)
        => !string.IsNullOrWhiteSpace(img) && Uri.IsWellFormedUriString(img, UriKind.Absolute);
}