"""
Contains the Gwendolyn class, a subclass of the discord command bot.

*Classes*
---------
  Gwendolyn(discord.ext.commands.Bot)
"""
import os  # Used for loading cogs in Gwendolyn.addCogs
import discord  # Used for discord.Intents and discord.Status
import interactions  # Used to initialized SlashCommands object
import git  # Used to pull when stopping

from discord.ext import commands  # Used to inherit from commands.bot
from pymongo import MongoClient  # Used for database management
from gwendolyn_old.funcs import Money, StarWars, Games, Other, LookupFuncs
from gwendolyn_old.utils import (get_options, get_credentials, log_this,
               DatabaseFuncs, EventHandler, ErrorHandler,
               long_strings)


class Gwendolyn(commands.Bot):
  """
  A multifunctional Discord bot.

  *Methods*
  ---------
    log(messages: Union[str, list], channel: str = "",
      level: int = 20)
    stop(ctx: interactions.SlashContext)
    defer(ctx: interactions.SlashContext)
  """
  # pylint: disable=too-many-instance-attributes

  def __init__(self):
    """Initialize the bot."""
    intents = discord.Intents.default()
    intents.members = True
    initiation_parameters = {
      "command_prefix": " ",
      "case_insensitive": True,
      "intents": intents,
      "status": discord.Status.dnd
    }
    super().__init__(**initiation_parameters)

    self._add_clients_and_options()
    self._add_util_classes()
    self._add_function_containers()
    self._add_cogs()

  def _add_clients_and_options(self):
    """Add all the client, option and credentials objects."""
    self.long_strings = long_strings()
    self.options = get_options()
    self.credentials = get_credentials()
    mongo_user = self.credentials["mongo_db_user"]
    mongo_password = self.credentials["mongo_db_password"]
    mongo_url = f"mongodb+srv://{mongo_user}:{mongo_password}@gwendolyn"
    mongo_url += ".qkwfy.mongodb.net/Gwendolyn?retryWrites=true&w=majority"
    database_clint = MongoClient(mongo_url)

    if self.options["testing"]:
      self.log("Testing mode")
      self.database = database_clint["Gwendolyn-Test"]
    else:
      self.database = database_clint["Gwendolyn"]

  def _add_util_classes(self):
    """Add all the classes used as utility."""
    self.database_funcs = DatabaseFuncs(self)
    self.event_handler = EventHandler(self)
    self.error_handler = ErrorHandler(self)
    slash_parameters = {
      "sync_commands": True,
      "sync_on_cog_reload": True,
      "override_type": True
    }
    self.slash = interactions.SlashCommand(self, **slash_parameters)

  def _add_function_containers(self):
    """Add all the function containers used for commands."""
    self.star_wars = StarWars(self)
    self.other = Other(self)
    self.lookup_funcs = LookupFuncs(self)
    self.games = Games(self)
    self.money = Money(self)

  def _add_cogs(self):
    """Load cogs."""
    for filename in os.listdir("./gwendolyn/cogs"):
      if filename.endswith(".py"):
        self.load_extension(f"gwendolyn.cogs.{filename[:-3]}")

  def log(self, messages, channel: str = "", level: int = 20): # pylint:disable=no-self-use
    """Log a message. Described in utils/util_functions.py."""
    log_this(messages, channel, level)

  async def stop(self, ctx: interactions.SlashContext):
    """
    Stop the bot, and stop running games.

    Only stops the bot if the user in ctx.author is one of the
    admins given in options.txt.

    *parameters*
    ------------
      ctx: interactions.SlashContext
        The context of the "/stop" slash command.
    """
    if f"#{ctx.author.id}" in self.options["admins"]:
      await ctx.send("Pulling git repo and restarting...")

      await self.change_presence(status=discord.Status.offline)

      self.database_funcs.wipe_games()
      if not self.options["testing"]:
        git_client = git.cmd.Git("")
        git_client.pull()

      self.log("Logging out", level=25)
      await self.close()
    else:
      log_message = f"{ctx.author.display_name} tried to stop me!"
      self.log(log_message, str(ctx.channel_id))
      await ctx.send(f"I don't think I will, {ctx.author.display_name}")

  async def defer(self, ctx: interactions.SlashContext):
    """Send a "Gwendolyn is thinking" message to the user."""
    try:
      await ctx.defer()
    except interactions.error.AlreadyResponded:
      self.log("defer failed")
