using Grpc.Core;
using EllieBot.Db.Models;
using EllieBot.Modules.Administration.Services;
using Enum = System.Enum;

namespace EllieBot.GrpcApi;

public sealed class WarnSvc : GrpcWarn.GrpcWarnBase, IGrpcSvc, IEService
{
    private readonly UserPunishService _ups;
    private readonly DiscordSocketClient _client;

    public WarnSvc(UserPunishService ups, DiscordSocketClient client)
    {
        _ups = ups;
        _client = client;
    }

    public ServerServiceDefinition Bind()
        => GrpcWarn.BindService(this);

    public override async Task<WarnSettingsReply> GetWarnSettings(
        WarnSettingsRequest request,
        ServerCallContext context)
    {
        var list = await _ups.WarnPunishList(request.GuildId);

        var wsr = new WarnSettingsReply();

        (wsr.ExpiryDays, wsr.DeleteOnExpire) = await _ups.GetWarnExpire(request.GuildId);

        wsr.Punishments.AddRange(list.Select(x => new WarnPunishment()
        {
            Action = x.Punishment.ToString(),
            Duration = x.Time,
            Threshold = x.Count,
            Role = x.RoleId is ulong rid
                ? _client.GetGuild(request.GuildId)?.GetRole(rid)?.Name ?? x.RoleId?.ToString() ?? string.Empty
                : string.Empty
        }));

        return wsr;
    }

    public override async Task<SetWarnExpiryReply> SetWarnExpiry(
        SetWarnExpiryRequest request,
        ServerCallContext context)
    {
        if (request.ExpiryDays > 366)
        {
            return new SetWarnExpiryReply()
            {
                Success = false
            };
        }

        await _ups.WarnExpireAsync(request.GuildId, request.ExpiryDays, request.DeleteOnExpire);

        return new SetWarnExpiryReply()
        {
            Success = true
        };
    }

    public override async Task<DeleteWarnpReply> DeleteWarnp(DeleteWarnpRequest request, ServerCallContext context)
    {
        var succ = await _ups.WarnPunishRemove(request.GuildId, request.Threshold);

        return new DeleteWarnpReply
        {
            Success = succ
        };
    }

    public override async Task<AddWarnpReply> AddWarnp(AddWarnpRequest request, ServerCallContext context)
    {
        if (request.Punishment.Threshold <= 0)
            throw new RpcException(new Status(StatusCode.InvalidArgument, "Threshold must be greater than 0"));

        var g = _client.GetGuild(request.GuildId);

        if (g is null)
            throw new RpcException(new Status(StatusCode.NotFound, "Guild not found"));

        if (!Enum.TryParse<PunishmentAction>(request.Punishment.Action, out var action))
            throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid action"));

        IRole? role = null;
        if (action == PunishmentAction.AddRole && ulong.TryParse(request.Punishment.Role, out var roleId))
        {
            role = g.GetRole(roleId);

            if (role is null)
                return new AddWarnpReply()
                {
                    Success = false
                };

            if (!ulong.TryParse(context.RequestHeaders.GetValue("userid"), out var userId))
                return new AddWarnpReply()
                {
                    Success = false
                };

            var user = await ((IGuild)g).GetUserAsync(userId);

            if (user is null)
                throw new RpcException(new Status(StatusCode.NotFound, "User not found"));

            var userMaxRole = user.GetRoles().MaxBy(x => x.Position)?.Position ?? 0;
            if (g.OwnerId != user.Id && userMaxRole <= role.Position)
            {
                return new AddWarnpReply()
                {
                    Success = false
                };
            }
        }

        var duration = TimeSpan.FromMinutes(request.Punishment.Duration);

        var succ = await _ups.WarnPunish(request.GuildId,
            request.Punishment.Threshold,
            action,
            duration,
            role
        );

        return new AddWarnpReply()
        {
            Success = succ
        };
    }

    public override async Task<GetLatestWarningsReply> GetLatestWarnings(
        GetLatestWarningsRequest request,
        ServerCallContext context)
    {
        var (latest, count) = await _ups.GetLatestWarnings(request.GuildId, request.Page);

        var reply = new GetLatestWarningsReply()
        {
            TotalCount = count
        };

        reply.Warnings.AddRange(latest.Select(MapWarningToGrpcWarning));

        return reply;
    }

    public override async Task<GetUserWarningsReply> GetUserWarnings(
        GetUserWarningsRequest request,
        ServerCallContext context)
    {
        IReadOnlyCollection<Db.Models.Warning> latest = [];
        var count = 0;
        if (ulong.TryParse(request.User, out var userId))
        {
            (latest, count) = await _ups.GetUserWarnings(request.GuildId, userId, request.Page);
        }
        else if (_client.GetGuild(request.GuildId)?.Users.FirstOrDefault(x => x.Username == request.User) is { } user)
        {
            (latest, count) = await _ups.GetUserWarnings(request.GuildId, user.Id, request.Page);
        }
        else
        {
        }

        var reply = new GetUserWarningsReply
        {
            TotalCount = count
        };

        reply.Warnings.AddRange(latest.Select(MapWarningToGrpcWarning));

        return reply;
    }

    private Warning MapWarningToGrpcWarning(Db.Models.Warning x)
    {
        return new Warning
        {
            Id = new kwum(x.Id).ToString(),
            Forgiven = x.Forgiven,
            ForgivenBy = x.ForgivenBy ?? string.Empty,
            Reason = x.Reason ?? string.Empty,
            Timestamp = x.DateAdded is { } da ? Ellie.Common.Extensions.ToTimestamp(da) : 0,
            Weight = x.Weight,
            Moderator = x.Moderator ?? string.Empty,
            User = _client.GetUser(x.UserId)?.Username ?? x.UserId.ToString(),
            UserId = x.UserId
        };
    }

    public override async Task<ForgiveWarningReply> ForgiveWarning(
        ForgiveWarningRequest request,
        ServerCallContext context)
    {
        if (!kwum.TryParse(request.WarnId, out var wid))
            throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid warning ID"));

        var succ = await _ups.ForgiveWarning(request.GuildId, wid, request.ModName);

        return new ForgiveWarningReply
        {
            Success = succ
        };
    }

    public override async Task<ForgiveWarningReply> DeleteWarning(
        ForgiveWarningRequest request,
        ServerCallContext context)
    {
        if (!kwum.TryParse(request.WarnId, out var wid))
            throw new RpcException(new Status(StatusCode.InvalidArgument, "Invalid warning ID"));

        var succ = await _ups.WarnDelete(request.GuildId, wid);

        return new ForgiveWarningReply
        {
            Success = succ
        };
    }
}