using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using EllieBot.Modules.Gambling.Services;
using EllieBot.Modules.Xp.Services;

namespace EllieBot.GrpcApi;

public static class GrpcApiExtensions
{
    public static ulong GetUserId(this ServerCallContext context)
        => ulong.Parse(context.RequestHeaders.FirstOrDefault(x => x.Key == "userid")!.Value);
}

public sealed class OtherSvc : GrpcOther.GrpcOtherBase, IEService
{
    private readonly IDiscordClient _client;
    private readonly XpService _xp;
    private readonly ICurrencyService _cur;
    private readonly WaifuService _waifus;
    private readonly ICoordinator _coord;
    private readonly IStatsService _stats;
    private readonly IBotCache _cache;

    public OtherSvc(
        DiscordSocketClient client,
        XpService xp,
        ICurrencyService cur,
        WaifuService waifus,
        ICoordinator coord,
        IStatsService stats,
        IBotCache cache)
    {
        _client = client;
        _xp = xp;
        _cur = cur;
        _waifus = waifus;
        _coord = coord;
        _stats = stats;
        _cache = cache;
    }

    [GrpcNoAuthRequired]
    public override async Task<BotOnGuildReply> BotOnGuild(BotOnGuildRequest request, ServerCallContext context)
    {
        var guild = await _client.GetGuildAsync(request.GuildId);

        var reply = new BotOnGuildReply
        {
            Success = guild is not null
        };

        return reply;
    }

    public override async Task<GetTextChannelsReply> GetTextChannels(
        GetTextChannelsRequest request,
        ServerCallContext context)
    {
        var g = await _client.GetGuildAsync(request.GuildId);
        var reply = new GetTextChannelsReply();

        var chs = await g.GetTextChannelsAsync();

        reply.TextChannels.AddRange(chs.Select(x => new TextChannelReply()
        {
            Id = x.Id,
            Name = x.Name,
        }));

        return reply;
    }

    [GrpcNoAuthRequired]
    public override async Task<GetGuildsReply> GetGuilds(Empty request, ServerCallContext context)
    {
        var guilds = await _client.GetGuildsAsync(CacheMode.CacheOnly);

        var reply = new GetGuildsReply();
        var userId = context.GetUserId();

        var toReturn = new List<IGuild>();
        foreach (var g in guilds)
        {
            var user = await g.GetUserAsync(userId);
            if (user is not null && user.GuildPermissions.Has(GuildPermission.Administrator))
                toReturn.Add(g);
        }

        reply.Guilds.AddRange(toReturn
            .Select(x => new GuildReply()
            {
                Id = x.Id,
                Name = x.Name,
                IconUrl = x.IconUrl
            }));

        return reply;
    }


    [GrpcNoAuthRequired]
    public override async Task<CurrencyLbReply> GetCurrencyLb(GetLbRequest request, ServerCallContext context)
    {
        var users = await _cur.GetTopRichest(_client.CurrentUser.Id, request.Page, request.PerPage);

        var reply = new CurrencyLbReply();
        var entries = users.Select(async x =>
        {
            var user = await _client.GetUserAsync(x.UserId, CacheMode.CacheOnly);
            return new CurrencyLbEntryReply()
            {
                Amount = x.CurrencyAmount,
                User = user?.ToString() ?? x.Username,
                UserId = x.UserId,
                Avatar = user?.RealAvatarUrl().ToString() ?? x.RealAvatarUrl()?.ToString()
            };
        });

        reply.Entries.AddRange(await entries.WhenAll());

        return reply;
    }

    [GrpcNoAuthRequired]
    public override async Task<XpLbReply> GetXpLb(GetLbRequest request, ServerCallContext context)
    {
        var users = await _xp.GetGlobalUserXps(request.Page);

        var reply = new XpLbReply();

        var entries = users.Select(x =>
        {
            var lvl = new LevelStats(x.TotalXp);

            return new XpLbEntryReply()
            {
                Level = lvl.Level,
                TotalXp = x.TotalXp,
                User = x.Username,
                UserId = x.UserId
            };
        });

        reply.Entries.AddRange(entries);

        return reply;
    }

    [GrpcNoAuthRequired]
    public override async Task<WaifuLbReply> GetWaifuLb(GetLbRequest request, ServerCallContext context)
    {
        var waifus = await _waifus.GetTopWaifusAtPage(request.Page, request.PerPage);

        var reply = new WaifuLbReply();
        reply.Entries.AddRange(waifus.Select(x => new WaifuLbEntry()
        {
            ClaimedBy = x.ClaimerName ?? string.Empty,
            IsMutual = x.ClaimerName == x.Affinity,
            Value = x.Price,
            User = x.WaifuName,
        }));
        return reply;
    }

    [GrpcNoAuthRequired]
    public override async Task<GetShardStatusesReply> GetShardStatuses(Empty request, ServerCallContext context)
    {
        var reply = new GetShardStatusesReply();

        await _cache.GetOrAddAsync<List<ShardStatus>>("coord:statuses",
            () => Task.FromResult(_coord.GetAllShardStatuses().ToList())!,
            TimeSpan.FromMinutes(1));

        var shards = _coord.GetAllShardStatuses();

        reply.Shards.AddRange(shards.Select(x => new ShardStatusReply()
        {
            Id = x.ShardId,
            Status = x.ConnectionState.ToString(),
            GuildCount = x.GuildCount,
            LastUpdate = Timestamp.FromDateTime(x.LastUpdate),
        }));


        return reply;
    }

    public override async Task<GetServerInfoReply> GetServerInfo(ServerInfoRequest request, ServerCallContext context)
    {
        var info = await _stats.GetGuildInfoAsync(request.GuildId);

        var reply = new GetServerInfoReply()
        {
            Id = info.Id,
            Name = info.Name,
            IconUrl = info.IconUrl,
            OwnerId = info.OwnerId,
            OwnerName = info.Owner,
            TextChannels = info.TextChannels,
            VoiceChannels = info.VoiceChannels,
            MemberCount = info.MemberCount,
            CreatedAt = info.CreatedAt.Ticks,
        };

        reply.Features.AddRange(info.Features);
        reply.Emojis.AddRange(info.Emojis.Select(x => new EmojiReply()
        {
            Name = x.Name,
            Url = x.Url,
            Code = x.ToString()
        }));

        reply.Roles.AddRange(info.Roles.Select(x => new RoleReply()
        {
            Id = x.Id,
            Name = x.Name,
            IconUrl = x.GetIconUrl() ?? string.Empty,
            Color = x.Color.ToString()
        }));

        return reply;
    }
}