text: import traceback
  from datetime import datetime
  from math import floor
  from typing import Union
  
  import discord
  from data.services import user_service
  from discord.commands import (Option, errors, message_command, slash_command,
                                user_command)
  from discord.ext import commands
  from discord.utils import format_dt
  from utils import BlooContext, cfg
  from utils.permissions import (PermissionsFailure, permissions, user_resolver,
                                 whisper)
  
  
  class UserInfo(commands.Cog):
      def __init__(self, bot):
          self.bot = bot
          self.start_time = datetime.now()
  
      @whisper()
      @slash_command(guild_ids=[cfg.guild_id], description="Get avatar of another user or yourself.")
      async def avatar(self, ctx: BlooContext, user: Option(discord.Member, description="User to get avatar of", required=False)) -> None:
          if not user:
              user = ctx.user
          embed = discord.Embed(title=f"{user}'s Avatar", color=discord.Color.random())
          embed.set_image(url=user.avatar)
          embed.set_footer(text=f"Requested by {ctx.author}")
          await ctx.respond(embed=embed, ephemeral=ctx.whisper)
  
      @whisper()
      @slash_command(guild_ids=[cfg.guild_id], description="Get info of another user or yourself.")
      async def userinfo(self, ctx: BlooContext, user: Option(discord.Member, description="User to get info of", required=False)) -> None:
          await self.handle_userinfo(ctx, user)
      
      @whisper()
      @user_command(guild_ids=[cfg.guild_id], name="Userinfo")
      async def userinfo_rc(self, ctx: BlooContext, user: discord.Member) -> None:
          await self.handle_userinfo(ctx, user)
      
      @whisper()
      @message_command(guild_ids=[cfg.guild_id], name="Userinfo")
      async def userinfo_msg(self, ctx: BlooContext, message: discord.Message) -> None:
          await self.handle_userinfo(ctx, message.author)
  
      async def handle_userinfo(self, ctx: BlooContext, user: Union[discord.User, discord.Member]):
          is_mod = permissions.has(ctx.guild, ctx.author, 5)
          if user is None:
              user = ctx.author
          elif isinstance(user, str) or isinstance(user, int):
              user = await user_resolver(ctx, user)
  
          # is the invokee in the guild?
          if isinstance(user, discord.User) and not is_mod:
              raise commands.BadArgument("You do not have permission to use this command.")
  
          # non-mods are only allowed to request their own userinfo
          if not is_mod and user.id != ctx.author.id:
              raise commands.BadArgument(
                  "You do not have permission to use this command.")
  
          # prepare list of roles and join date
          roles = ""
          if isinstance(user, discord.Member) and user.joined_at is not None:
              reversed_roles = user.roles
              reversed_roles.reverse()
  
              for role in reversed_roles[:-1]:
                  roles += role.mention + " "
              joined = f"{format_dt(user.joined_at, style='F')} ({format_dt(user.joined_at, style='R')})"
          else:
              roles = "No roles."
              joined = f"User not in {ctx.guild}"
  
          results = user_service.get_user(user.id)
  
          embed = discord.Embed(title=f"User Information", color=user.color)
          embed.set_author(name=user)
          embed.set_thumbnail(url=user.avatar)
          embed.add_field(name="Username",
                          value=f'{user} ({user.mention})', inline=True)
          embed.add_field(
              name="Level", value=results.level if not results.is_clem else "0", inline=True)
          embed.add_field(
              name="XP", value=results.xp if not results.is_clem else "0/0", inline=True)
          embed.add_field(
              name="Roles", value=roles if roles else "None", inline=False)
          embed.add_field(
              name="Join date", value=joined, inline=True)
          embed.add_field(name="Account creation date",
                          value=f"{format_dt(user.created_at, style='F')} ({format_dt(user.created_at, style='R')})", inline=True)
          embed.set_footer(text=f"Requested by {ctx.author}")
          await ctx.respond(embed=embed, ephemeral=ctx.whisper)
  
      @whisper()
      @slash_command(guild_ids=[cfg.guild_id], description="Show your or another user's XP")
      async def xp(self, ctx: BlooContext, user: Option(discord.Member, description="Member to show xp of", required=False)):
          """Show your or another user's XP
  
          Example usage
          --------------
          !xp <@user/ID (optional)
  
          Parameters
          ----------
          user : discord.Member, optional
              "User to get XP of, by default None"
  
          """
  
          if user is None:
              user = ctx.author
  
          results = user_service.get_user(user.id)
  
          embed = discord.Embed(title="Level Statistics")
          embed.color = user.top_role.color
          embed.set_author(name=user, icon_url=user.display_avatar)
          embed.add_field(
              name="Level", value=results.level if not results.is_clem else "0", inline=True)
          embed.add_field(
              name="XP", value=f'{results.xp}/{xp_for_next_level(results.level)}' if not results.is_clem else "0/0", inline=True)
          rank, overall = user_service.leaderboard_rank(results.xp)
          embed.add_field(
              name="Rank", value=f"{rank}/{overall}" if not results.is_clem else f"{overall}/{overall}", inline=True)
          embed.set_footer(text=f"Requested by {ctx.author}")
  
          await ctx.respond(embed=embed, ephemeral=ctx.whisper)
  
      @whisper()
      @slash_command(guild_ids=[cfg.guild_id], description="Show your or another user's warnpoints")
      async def warnpoints(self, ctx: BlooContext, user: Option(discord.Member, description="Member to show warnpoints of", required=False)):
          """Show a user's warnpoints (mod only)
  
          Example usage
          --------------
          !warnpoints <@user/ID>
  
          Parameters
          ----------
          user : discord.Member
              "User whose warnpoints to show"
  
          """
  
          # if an invokee is not provided in command, call command on the invoker
          # (get invoker's warnpoints)
          user = user or ctx.author
  
          # users can only invoke on themselves if they aren't mods
          if not permissions.has(ctx.guild, ctx.author, 5) and user.id != ctx.author.id:
              raise PermissionsFailure(
                  f"You don't have permissions to check others' warnpoints.")
  
          # fetch user profile from database
          results = user_service.get_user(user.id)
  
          embed = discord.Embed(title="Warn Points")
          embed.color = discord.Color.orange()
          embed.set_thumbnail(url=user.avatar)
          embed.add_field(
              name="Member", value=f'{user.mention}\n{user}\n({user.id})', inline=True)
          embed.add_field(name="Warn Points",
                          value=results.warn_points, inline=True)
          embed.set_footer(text=f"Requested by {ctx.author}")
  
          await ctx.respond(embed=embed, ephemeral=ctx.whisper)
  
      # @cases.error
      @userinfo_rc.error
      @userinfo_msg.error
      @userinfo.error
      @warnpoints.error
      @xp.error
      # @xptop.error
      async def info_error(self,  ctx: BlooContext, error):
          if isinstance(error, errors.ApplicationCommandInvokeError):
              error = error.original
          
          if (isinstance(error, commands.MissingRequiredArgument)
              or isinstance(error, PermissionsFailure)
              or isinstance(error, commands.BadArgument)
              or isinstance(error, commands.BadUnionArgument)
              or isinstance(error, commands.MissingPermissions)
              or isinstance(error, commands.BotMissingPermissions)
              or isinstance(error, commands.MaxConcurrencyReached)
                  or isinstance(error, commands.NoPrivateMessage)):
              await ctx.send_error(error)
          else:
              await ctx.send_error("A fatal error occured. Tell <@109705860275539968> about this.")
              traceback.print_exc()
  
  
  def xp_for_next_level(_next):
      """Magic formula to determine XP thresholds for levels
      """
  
      level = 0
      xp = 0
  
      for _ in range(0, _next):
          xp = xp + 45 * level * (floor(level / 10) + 1)
          level += 1
  
      return xp
  
  
  def setup(bot):
      bot.add_cog(UserInfo(bot))
  