"""Plex integration with the bot."""
from math import floor, ceil
import time
import asyncio
import requests
import imdb
import discord
import xmltodict

from interactions.utils.manage_components import (create_button,
create_actionrow)
from interactions.model import ButtonStyle

from gwendolyn_old.utils import encode_id

class Plex():
  """Container for Plex functions and commands."""
  def __init__(self,bot):
    self.bot = bot
    self.credentials = self.bot.credentials
    self.long_strings = self.bot.long_strings
    server_ip = ["localhost", "192.168.0.40"][self.bot.options["testing"]]

    self.radarr_url = "http://"+server_ip+":7878/api/v3/"
    self.sonarr_url = "http://"+server_ip+":8989/api/"
    self.qbittorrent_url = "http://"+server_ip+":8080/api/v2/"
    self.movie_path = "/media/plex/Server/movies/"
    self.show_path = "/media/plex/Server/Shows/"


  async def request_movie(self, ctx, movie_name):
    """Request a movie for the Plex Server"""
    await self.bot.defer(ctx)

    self.bot.log("Searching for "+movie_name)
    movie_list = imdb.IMDb().search_movie(movie_name)
    movies = []
    for movie in movie_list:
      if movie["kind"] == "movie":
        movies.append(movie)
    if len(movies) > 5:
      movies = movies[:5]

    if len(movies) == 1:
      message_title = "**Is it this movie?**"
    else:
      message_title = "**Is it any of these movies?**"

    message_text = ""
    imdb_ids = []

    for i, movie in enumerate(movies):
      try:
        message_text += "\n"+str(i+1)+") "+movie["title"]
        try:
          message_text += " ("+str(movie["year"])+")"
        except KeyError:
          self.bot.log(f"{movie['title']} has no year.")
      except KeyError:
        message_text += "Error"
      imdb_ids.append(movie.movieID)

    self.bot.log(
      f"Returning a list of {len(movies)} possible movies: {imdb_ids}"
    )

    embed = discord.Embed(
      title=message_title,
      description=message_text,
      colour=0x00FF00
    )

    buttons = []
    if len(movies) == 1:
      buttons.append(create_button(
        style=ButtonStyle.green,
        label="✓",
        custom_id=encode_id(["plex", "movie", str(imdb_ids[0])])
        )
      )
    else:
      for i in range(len(movies)):
        buttons.append(
          create_button(
            style=ButtonStyle.blue,
            label=str(i+1),
            custom_id=encode_id(["plex", "movie", str(imdb_ids[i])])
          )
        )

    buttons.append(create_button(
      style=ButtonStyle.red,
      label="X",
      custom_id=encode_id(["plex", "movie", "x"])
      )
    )

    action_rows = []
    for i in range(((len(buttons)-1)//5)+1):
      action_rows.append(
        create_actionrow(
          *buttons[(i*5):(min(len(buttons),i*5+5))]
        )
      )

    await ctx.send(embed=embed, components=action_rows)




  async def add_movie(self, message, imdb_id, edit_message = True):
    """Add a movie to Plex server."""

    if not edit_message:
      await message.delete()

    if imdb_id == "X":
      self.bot.log("Did not find what the user was searching for")
      message_text = "Try searching for the IMDB id"
    else:
      self.bot.log("Trying to add movie "+str(imdb_id))

      # Searches for the movie using the imdb id through Radarr
      api_key = self.credentials["radarr_key"]
      request_url = self.radarr_url+"movie/lookup/imdb?imdbId=tt"+imdb_id
      request_url += "&apiKey="+api_key
      response = requests.get(request_url)

      # Makes the dict used for the post request
      lookup_data = response.json()
      post_data = {
        "qualityProfileId": 1,
        "rootFolderPath" : self.movie_path,
        "monitored" : True,
        "addOptions": {"searchForMovie": True}
      }
      for key in ["tmdbId","title","titleSlug","images","year"]:
        post_data.update({key : lookup_data[key]})

      # Makes the post request
      response = requests.post(
        url = self.radarr_url+"movie?apikey="+api_key,
        json = post_data
      )

      # Deciphers the response
      if response.status_code == 201:
        self.bot.log("Added "+post_data["title"]+" to Plex")
        message_text = "{} successfully added to Plex".format(
          post_data["title"]
        )
      elif response.status_code == 400:
        self.bot.log("The movie was already on plex")
        message_text = self.long_strings["Already on Plex"].format(
          post_data['title']
        )
      else:
        self.bot.log(str(response.status_code)+" "+response.reason)
        message_text = "Something went wrong",

    if edit_message:
      await message.edit(
        embed = None,
        content = message_text,
        components = []
      )
    else:
      await message.channel.send(message_text)

  async def request_show(self, ctx, show_name):
    """Request a show for the Plex server."""
    await self.bot.defer(ctx)

    self.bot.log("Searching for "+show_name)
    movies = imdb.IMDb().search_movie(show_name) # Replace with tvdb
    shows = []
    for movie in movies:
      if movie["kind"] in ["tv series","tv miniseries"]:
        shows.append(movie)
    if len(shows) > 5:
      shows = shows[:5]

    if len(shows) == 1:
      message_title = "**Is it this show?**"
    else:
      message_title = "**Is it any of these shows?**"

    message_text = ""
    imdb_ids = []

    for i, show in enumerate(shows):
      try:
        message_text += f"\n{i+1}) {show['title']} ({show['year']})"
      except KeyError:
        try:
          message_text += "\n"+str(i+1)+") "+show["title"]
        except KeyError:
          message_text += "Error"
      imdb_ids.append(show.movieID)

    self.bot.log(
      f"Returning a list of {len(shows)} possible shows: {imdb_ids}"
    )

    embed = discord.Embed(
      title=message_title,
      description=message_text,
      colour=0x00FF00
    )

    buttons = []
    if len(shows) == 1:
      buttons.append(create_button(
        style=ButtonStyle.green,
        label="✓",
        custom_id=encode_id(["plex", "show", str(imdb_ids[0])])
        )
      )
    else:
      for i in range(len(shows)):
        buttons.append(
          create_button(
            style=ButtonStyle.blue,
            label=str(i+1),
            custom_id=encode_id(["plex", "show", str(imdb_ids[i])])
          )
        )

    buttons.append(create_button(
      style=ButtonStyle.red,
      label="X",
      custom_id=encode_id(["plex", "show", "x"])
      )
    )

    action_rows = []
    for i in range(((len(buttons)-1)//5)+1):
      action_rows.append(
        create_actionrow(
          *buttons[(i*5):(min(len(buttons),i*5+5))]
        )
      )


    await ctx.send(embed=embed, components=action_rows)

  async def add_show(self, message, imdb_id, edit_message = True):
    """Add the requested show to Plex."""
    if imdb_id == "X":
      self.bot.log("Did not find what the user was searching for")
      message_text = "Try searching for the IMDB id"
    else:
      self.bot.log("Trying to add show "+str(imdb_id))

      # Finds the tvdb id
      tvdb_api_url = "https://thetvdb.com/api/"
      tvdb_method = "GetSeriesByRemoteID.php"
      tvdb_request_url = f"{tvdb_api_url}{tvdb_method}"
      tvdb_id = xmltodict.parse(
        requests.get(
          tvdb_request_url+f"?imdbid=tt{imdb_id}",
          headers = {"ContentType" : "application/json"}
        ).text
      )['Data']['Series']['seriesid']

      # Finds the rest of the information using Sonarr
      api_key = self.credentials["sonarr_key"]
      request_url = self.sonarr_url+"series/lookup?term="
      request_url += f"tvdb:{tvdb_id}"
      request_url += "&apiKey="+api_key
      response = requests.get(request_url)

      # Makes the dict used for the post request
      lookup_data = response.json()[0]
      post_data = {
        "ProfileId" : 1,
        "rootFolder_path" : self.show_path,
        "monitored" : True,
        "addOptions" : {"searchForMissingEpisodes" : True}
      }
      for key in ["tvdbId","title","titleSlug","images","seasons"]:
        post_data.update({key : lookup_data[key]})

      # Makes the post request
      response = requests.post(
        url= self.sonarr_url+"series?apikey="+api_key,
        json = post_data
      )

      # Deciphers the response
      if response.status_code == 201:
        self.bot.log("Added a "+post_data["title"]+" to Plex")
        message_text = post_data["title"]+" successfully added to Plex"
      elif response.status_code == 400:
        message_text = self.long_strings["Already on Plex"].format(
          post_data['title']
        )
      else:
        self.bot.log(str(response.status_code)+" "+response.reason)
        message_text = "Something went wrong"

    if edit_message:
      await message.edit(
        embed = None,
        content = message_text,
        components = []
      )
    else:
      await message.channel.send(message_text)

  async def __generate_download_list(self, show_dm, show_movies, show_shows,
                     episodes):
    """Generate a list of all torrents.

    *Returns*
      message_text: str
        A formatted list of all torrents

      all_downloaded: bool
        Whether all torrents are downloaded
      """
    self.bot.log("Generating torrent list")
    title_width = 100
    message = []
    all_downloaded = True

    if show_dm:
      message.append("")
      dm_section_title = "*Torrent Downloads*"
      dm_section_title_line = "-"*((title_width-len(dm_section_title))//2)
      message.append(
        dm_section_title_line+dm_section_title+dm_section_title_line
      )
      login_url = self.qbittorrent_url+"auth/login"
      username = self.credentials["qbittorrent_username"]
      password = self.credentials["qbittorrent_password"]
      login_url += f"?username={username}&password={password}"
      cookie = {"SID": requests.get(login_url).cookies.values()[0]}

      response = requests.get(
        self.qbittorrent_url+"torrents/info",
        cookies=cookie
      )
      torrent_list = response.json()

      if len(torrent_list) > 0:
        for torrent in torrent_list:
          if torrent['category'] not in ["radarr", "tv-sonarr"]:
            break
          torrent_name = torrent["name"]
          if len(torrent_name) > 30:
            if torrent_name[26] == " ":
              torrent_name = torrent_name[:26]+"...."
            else:
              torrent_name = torrent_name[:27]+"..."
          while len(torrent_name) < 30:
            torrent_name += " "

          if torrent["size"] == 0:
            download_ratio = 0
          elif torrent["amount_left"] == 0:
            download_ratio = 1
          else:
            download_ratio = min(
              torrent["downloaded"]/torrent["size"],
              1
            )
          progress_bar = "|"+("█"*floor(download_ratio*20))
          while len(progress_bar) < 21:
            progress_bar += " "

          progress_bar += "| "+str(floor(download_ratio*100))+"%"

          while len(progress_bar) < 27:
            progress_bar += " "

          eta_in_seconds = torrent["eta"]

          if eta_in_seconds >= 8640000:
            eta = "∞"
          else:
            eta = ""
            if eta_in_seconds >= 86400:
              eta += str(floor(eta_in_seconds/86400))+"d "
            if eta_in_seconds >= 3600:
              eta += str(floor((eta_in_seconds%86400)/3600))+"h "
            if eta_in_seconds >= 60:
              eta += str(floor((eta_in_seconds%3600)/60))+"m "

            eta += str(eta_in_seconds%60)+"s"

          torrent_info = f"{torrent_name}  {progress_bar}  "
          torrent_info += f"(Eta: {eta})"

          if torrent["state"] == "stalledDL":
            torrent_info += "   (Stalled)"

          if not (download_ratio == 1 and
              torrent["last_activity"] < time.time()-7200):
            message.append(torrent_info)

          if download_ratio < 1 and torrent["state"] != "stalledDL":
            all_downloaded = False
      else:
        message.append("No torrents currently downloading")

    if show_movies:
      message.append("")
      movies_section_title = "*Missing movies not downloading*"
      movies_section_line = (
        "-"*((title_width-len(movies_section_title))//2)
      )
      message.append(
        movies_section_line+movies_section_title+movies_section_line
      )
      movie_list = requests.get(
        self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"]
      ).json()
      print(
        self.radarr_url+"movie?apiKey="+self.credentials["radarr_key"]
      )
      movie_queue = requests.get(
        self.radarr_url+"queue?apiKey="+self.credentials["radarr_key"]
      ).json()
      movie_queue_ids = []

      for queue_item in movie_queue["records"]:
        movie_queue_ids.append(queue_item["movieId"])

      for movie in movie_list:
        if (not movie["hasFile"] and
          movie["id"] not in movie_queue_ids):
          movie_name = movie["title"]
          if len(movie_name) > 40:
            if movie_name[36] == " ":
              movie_name = movie_name[:36]+"...."
            else:
              movie_name = movie_name[:37]+"..."

          while len(movie_name) < 41:
            movie_name += " "

          if movie["monitored"]:
            movie_info = movie_name+"Could not find a torrent"
          else:
            movie_info = self.long_strings["No torrent"].format(
              movie_name
            )

          message.append(movie_info)

    if show_shows:
      message.append("")
      show_section_title = "*Missing shows not downloading*"
      show_section_line = "-"*((title_width-len(show_section_title))//2)
      message.append(
        show_section_line+show_section_title+show_section_line
        )

      show_list = requests.get(
        self.sonarr_url+"series?apiKey="+self.credentials["sonarr_key"]
      ).json()

      for show in show_list:
        if show["seasons"][0]["seasonNumber"] == 0:
          seasons = show["seasons"][1:]
        else:
          seasons = show["seasons"]
        if any(
          (
            i["statistics"]["episodeCount"] !=
            i["statistics"]["totalEpisodeCount"]
          ) for i in seasons):
          if all(
            i["statistics"]["episodeCount"] == 0 for i in seasons
            ):
            message.append(show["title"] + " (all episodes)")
          else:
            if episodes:
              missing_episodes = sum(
                (i["statistics"]["totalEpisodeCount"] -
                 i["statistics"]["episodeCount"])
                for i in seasons)
              message.append(
                f"{show['title']} ({missing_episodes} episodes)"
              )

    message.append("-"*title_width)

    message_text = "```"+"\n".join(message[1:])+"```"
    if message_text == "``````":
      message_text = self.long_strings["No torrents downloading"]
    return message_text, all_downloaded

  async def downloading(self, ctx, content):
    """Send message with list of all downloading torrents."""
    async def send_long_message(ctx,message_text):
      if len(message_text) <= 1994:
        await ctx.send("```"+message_text+"```")
      else:
        cut_off_index = message_text[:1994].rfind("\n")
        await ctx.send("```"+message_text[:cut_off_index]+"```")
        await send_long_message(ctx,message_text[cut_off_index+1:])

    await self.bot.defer(ctx)

    # showDM, showMovies, showShows, episodes
    parameters = [False, False, False, False]
    show_dm_args = ["d", "dm", "downloading", "downloadmanager"]
    show_movies_args = ["m", "movies"]
    show_shows_args = ["s", "shows", "series"]
    show_episode_args = ["e", "episodes"]
    arg_list = [
      show_dm_args, show_movies_args, show_shows_args, show_episode_args
    ]
    input_args = []
    valid_arguments = True

    while content != "" and valid_arguments:
      if content[0] == " ":
        content = content[1:]
      elif content[0] == "-":
        if content[1] == "-":
          arg_start = 2
          if " " in content:
            arg_stop = content.find(" ")
          else:
            arg_stop = None
        else:
          arg_start = 1
          arg_stop = 2

        input_args.append(content[arg_start:arg_stop])
        if arg_stop is None:
          content = ""
        else:
          content = content[arg_stop:]
      else:
        valid_arguments = False

    if valid_arguments:
      for arg_index, arg_aliases in enumerate(arg_list):
        arg_in_input = [i in input_args for i in arg_aliases]
        if any(arg_in_input):
          input_args.remove(arg_aliases[arg_in_input.index(True)])
          parameters[arg_index] = True

    if len(input_args) != 0 or (not parameters[2] and parameters[3]):
      valid_arguments = False

    show_anything = any(i for i in parameters)
    if not (valid_arguments and show_anything):
      await ctx.send(self.long_strings["Invalid parameters"])
    else:
      message_text, all_downloaded = await self.__generate_download_list(
        *parameters
      )
      if not message_text.startswith("```"):
        await ctx.send(message_text)

      elif len(message_text) > 2000:
        message_text = message_text[3:-3]
        await send_long_message(ctx,message_text)

      elif all_downloaded:
        await ctx.send(message_text)

      else:
        updates_left = 60
        message_text = self.long_strings["Update"].format(
          message_text[:-3], ceil(updates_left/6)
        )
        old_message = await ctx.send(message_text)

        while ((not all_downloaded) and updates_left > 0):
          await asyncio.sleep(10)
          updates_left -= 1
          message_text, all_downloaded = await (
            self.__generate_download_list(*parameters)
          )
          message_text = self.long_strings["Update"].format(
            message_text[:-3],
            ceil(updates_left/6)
          )
          await old_message.edit(content = message_text)

        message_text, all_downloaded = await (
          self.__generate_download_list(*parameters)
        )

        if message_text.startswith("```"):
          if all_downloaded:
            self.bot.log("All torrents are downloaded")
          else:
            message_text = self.long_strings["No updates"].format(
              message_text[:-3]
            )
            self.bot.log("The message updated 20 times")

        await old_message.edit(content = message_text)
