defmodule Outlook.Translators do
  @moduledoc """
  The Translators context.
  """

  import Ecto.Query, warn: false
  alias Outlook.Repo

  alias Outlook.InternalTree.TranslationUnit
  alias Outlook.Translators.{DeeplAccount,Deepl}
  alias OutlookWeb.HtmlDocComponent

  def list_deepl_accounts do
    Repo.all(DeeplAccount)
  end

  def get_deepl_account!(id), do: Repo.get!(DeeplAccount, id)

  def get_deepl_auth_key(user) do
    deepl_account_for_user(user)
    |> select([:auth_key])
    |> Repo.one()
    |> Map.get(:auth_key)
  end

  def create_deepl_account(attrs \\ %{}) do
    %DeeplAccount{}
    |> DeeplAccount.changeset(attrs)
    |> Repo.insert()
  end

  def update_deepl_account(%DeeplAccount{} = deepl_account, attrs) do
    deepl_account
    |> DeeplAccount.changeset(attrs)
    |> Repo.update()
  end

  def delete_deepl_account(%DeeplAccount{} = deepl_account) do
    Repo.delete(deepl_account)
  end

  def change_deepl_account(%DeeplAccount{} = deepl_account, attrs \\ %{}) do
    DeeplAccount.changeset(deepl_account, attrs)
  end

  def increase_our_character_count(user, billed_characters) do
    deepl_account_for_user(user)
    |> Repo.update_all([inc: [our_character_count: billed_characters]])
  end

  def get_uptodate_deepl_counts(user) do
    deepl_counts = Deepl.get_usage_counts(get_deepl_auth_key(user))
    our_character_count = deepl_account_for_user(user)
    |> select([:our_character_count])
    |> Repo.one()
    |> Map.get(:our_character_count)
    most_accurate = max(our_character_count, deepl_counts.character_count)
    %{character_limit: deepl_counts.character_limit,
      character_count: deepl_counts.character_count,
      our_character_count: our_character_count,
      percent_used: most_accurate * 100 / deepl_counts.character_limit}
  end

  def translate(translation, current_user) do
    %{language: target_lang,
      article: %{content: article_tree, language: source_lang}
    } = translation
    article_as_html = prepare_article(article_tree)
    auth_key = get_deepl_auth_key(current_user.id)
    args = [
      self(),
      article_as_html,
      %{
        source_lang: source_lang,
        target_lang: target_lang,
        auth_key: auth_key
      }
    ]
    Task.start_link(Deepl, :translate, args)
  end

  def process_translation_result(result, tunit_ids, current_user) do
    increase_our_character_count(current_user, result.billed_characters)
    process_translation(result.translation, tunit_ids)
  end

  def update_deepl_counts(user, counts) do
    deepl_account_for_user(user)
    |> Repo.update_all([set: [
      character_limit: counts.character_limit,
      character_count: counts.character_count
    ]])
  end

  defp deepl_account_for_user(user) when is_struct(user), do: deepl_account_for_user(user.id)
  defp deepl_account_for_user(user_id) do
    DeeplAccount |> where(user_id: ^user_id)
  end

  defp prepare_article(tree) do
    # Logger.info "so far."
    HtmlDocComponent.render_doc(%{tree: tree, tunit_tag: "tunit"})
    |> Phoenix.HTML.Safe.to_iodata()
    |> IO.iodata_to_binary()
  end

  defp process_translation(translation, tunit_ids) do
    tunit_map = translation
    |> Floki.parse_fragment!
    |> Floki.find("tunit")
    |> Enum.map(fn {_,atts,cont} ->
      %TranslationUnit{
        nid: Enum.find(atts, fn {k,_} -> k == "nid" end) |> Tuple.to_list |> Enum.at(1),
        content: Floki.raw_html(cont),
        status: :untranslated
      }
    end)
    |> Enum.map(fn tunit -> {tunit.nid, tunit} end)
    |> Enum.into(%{})

    case Enum.sort(Map.keys(tunit_map)) == Enum.sort(tunit_ids) do
      true -> {:ok, tunit_map}
      false -> {:error, "keys don't equal the originals"}
    end
  end
end
