lib/glific/clients/kef.ex

defmodule Glific.Clients.KEF do
  @moduledoc """
  Tweak GCS Bucket name based on group that the contact is in (if any)
  """

  import Ecto.Query, warn: false

  require Logger

  alias Glific.{
    Contacts,
    Flows.ContactField,
    Partners,
    Partners.OrganizationData,
    Repo,
    Settings.Language,
    Sheets.ApiClient
  }

  @props %{
    worksheets: %{
      sheet_links: %{
        prekg:
          "https://docs.google.com/spreadsheets/d/e/2PACX-1vQPzJ4BruF8RFMB0DwBgM8Rer7MC0fiL_IVC0rrLtZT7rsa3UnGE3ZTVBRtNdZI9zGXGlQevCajwNcn/pub?gid=89165000&single=true&output=csv",
        lkg:
          "https://docs.google.com/spreadsheets/d/e/2PACX-1vQPzJ4BruF8RFMB0DwBgM8Rer7MC0fiL_IVC0rrLtZT7rsa3UnGE3ZTVBRtNdZI9zGXGlQevCajwNcn/pub?gid=531803735&single=true&output=csv",
        ukg:
          "https://docs.google.com/spreadsheets/d/e/2PACX-1vQPzJ4BruF8RFMB0DwBgM8Rer7MC0fiL_IVC0rrLtZT7rsa3UnGE3ZTVBRtNdZI9zGXGlQevCajwNcn/pub?gid=1715409890&single=true&output=csv"
      }
    },
    school_ids_sheet_link:
      "https://docs.google.com/spreadsheets/d/e/2PACX-1vQPzJ4BruF8RFMB0DwBgM8Rer7MC0fiL_IVC0rrLtZT7rsa3UnGE3ZTVBRtNdZI9zGXGlQevCajwNcn/pub?gid=1503063199&single=true&output=csv"
  }

  @doc """
  Generate custom GCS bucket name based on group that the contact is in (if any)
  """
  @spec gcs_file_name(map()) :: String.t()
  def gcs_file_name(media) do
    {:ok, contact} =
      Repo.fetch_by(Contacts.Contact, %{
        id: media["contact_id"],
        organization_id: media["organization_id"]
      })

    school_id = get_in(contact.fields, ["school_id", "value"])
    phone = contact.phone

    if is_nil(school_id),
      do: media["remote_name"],
      else: "schools/#{school_id}/#{phone}" <> "/" <> media["remote_name"]
  end

  @doc """
  Create a webhook with different signatures, so we can easily implement
  additional functionality as needed
  """
  @spec webhook(String.t(), map()) :: map()
  def webhook("load_worksheets", fields) do
    Glific.parse_maybe_integer!(fields["organization_id"])
    |> load_worksheets()

    fields
  end

  def webhook("load_school_ids", fields) do
    Glific.parse_maybe_integer!(fields["organization_id"])
    |> load_school_ids()

    fields
  end

  def webhook("validate_worksheet_code", fields) do
    status =
      Glific.parse_maybe_integer!(fields["organization_id"])
      |> validate_worksheet_code(fields["worksheet_code"])

    %{
      is_vaid: status
    }
  end

  def webhook("get_worksheet_info", fields) do
    Glific.parse_maybe_integer!(fields["organization_id"])
    |> get_worksheet_info(fields["worksheet_code"])
    |> interactive_message_reflection_question(fields["language_label"])
  end

  def webhook("get_interactive_message_reflection_question", fields) do
    Glific.parse_maybe_integer!(fields["organization_id"])
    |> get_worksheet_info(fields["worksheet_code"])
    |> interactive_message_reflection_question(fields["language_label"])
  end

  def webhook("validate_reflection_response", fields) do
    user_input = Glific.string_clean(fields["user_answer"])
    correct_answer = Glific.string_clean(fields["correct_answer"])

    in_valid_answer_range =
      fields["valid_answers"]
      |> String.split("|", trim: true)
      |> Enum.map(&Glific.string_clean(&1))
      |> Enum.member?(user_input)

    cond do
      user_input == correct_answer ->
        %{status: "correct_response"}

      correct_answer == "allanswers" && in_valid_answer_range ->
        %{status: "correct_response"}

      in_valid_answer_range ->
        %{status: "incorrect_response"}

      true ->
        %{status: "out_of_range"}
    end
  end

  def webhook("mark_worksheet_completed", fields) do
    worksheet_code = String.trim(fields["worksheet_code"] || "")
    worksheet_grade = String.trim(fields["worksheet_grade"] || "")
    contact_id = Glific.parse_maybe_integer!(get_in(fields, ["contact", "id"]))

    completed_worksheet_codes =
      get_in(fields, ["contact", "fields", "completed_worksheet_code", "value"]) || ""

    completed_worksheet_codes =
      if completed_worksheet_codes == "",
        do: worksheet_code,
        else: "#{completed_worksheet_codes}, #{worksheet_code}_#{worksheet_grade}"

    Contacts.get_contact!(contact_id)
    |> ContactField.do_add_contact_field(
      "completed_worksheet_code",
      "completed_worksheet_code",
      completed_worksheet_codes
    )

    %{
      error: false,
      message: "Worksheet #{worksheet_code}_#{worksheet_grade} marked as completed"
    }
  end

  def webhook("mark_helping_hand_complete", fields) do
    helping_hand_topic = String.trim(fields["helping_hand_topic"] || "")
    contact_id = Glific.parse_maybe_integer!(get_in(fields, ["contact", "id"]))

    completed_helping_hand_topics =
      get_in(fields, ["contact", "fields", "completed_helping_hand_topic", "value"]) || ""

    completed_helping_hand_topics =
      if completed_helping_hand_topics == "",
        do: helping_hand_topic,
        else: "#{completed_helping_hand_topics}, #{helping_hand_topic}"

    Contacts.get_contact!(contact_id)
    |> ContactField.do_add_contact_field(
      "completed_helping_hand_topic",
      "completed_helping_hand_topic",
      completed_helping_hand_topics
    )

    %{
      error: false,
      message: "Helping hand #{helping_hand_topic} marked as completed"
    }
  end

  def webhook("get_reports_info", fields) do
    language = get_language(fields["contact"]["id"])

    uniq_completed_worksheets =
      get_in(fields, ["contact", "fields"])
      |> get_completed_worksheets()

    uniq_completed_helping_hands =
      get_in(fields, ["contact", "fields"])
      |> get_completed_helping_hands()

    %{
      worksheet: %{
        completed: length(uniq_completed_worksheets),
        remaining: 36 - length(uniq_completed_worksheets),
        list: Enum.join(uniq_completed_worksheets, ","),
        list_message: get_worksheet_msg(uniq_completed_worksheets, language)
      },
      helping_hand: %{
        completed: length(uniq_completed_helping_hands),
        list: Enum.join(uniq_completed_helping_hands, ","),
        total: 1
      }
    }
  end

  def webhook("get_school_id_info", fields) do
    school_id = fields["school_id"] || ""

    Glific.parse_maybe_integer!(fields["organization_id"])
    |> get_school_id_info(school_id)
  end

  def webhook(_, _) do
    raise "Unknown webhook"
  end

  @spec load_worksheets(non_neg_integer()) :: map()
  defp load_worksheets(org_id) do
    @props.worksheets.sheet_links
    |> Enum.each(fn {k, v} -> do_load_code_worksheet(k, v, org_id) end)

    %{status: "successfull"}
  end

  @spec do_load_code_worksheet(String.t(), String.t(), non_neg_integer()) :: :ok
  defp do_load_code_worksheet(class, sheet_link, org_id) do
    ApiClient.get_csv_content(url: sheet_link)
    |> Enum.each(fn {_, row} ->
      row = Map.put(row, "class", class)
      row = Map.put(row, "code", row["Worksheet Code"])
      key = clean_worksheet_code(row["Worksheet Code"] || "")
      Partners.maybe_insert_organization_data(key, row, org_id)
    end)
  end

  @spec load_school_ids(non_neg_integer()) :: :ok
  defp load_school_ids(org_id) do
    ApiClient.get_csv_content(url: @props.school_ids_sheet_link)
    |> Enum.reduce(%{}, fn {_, row}, acc ->
      school_id = row["School ID"]

      if school_id in [nil, ""],
        do: acc,
        else: Map.put(acc, Glific.string_clean(school_id), clean_map_keys(row))
    end)
    |> then(fn school_ids_data ->
      Partners.maybe_insert_organization_data("school_ids_data", school_ids_data, org_id)
    end)

    :ok
  end

  @spec validate_worksheet_code(non_neg_integer(), String.t()) :: boolean()
  defp validate_worksheet_code(org_id, worksheet_code) do
    Repo.fetch_by(OrganizationData, %{
      organization_id: org_id,
      key: clean_worksheet_code(worksheet_code)
    })
    |> case do
      {:ok, _data} -> true
      _ -> false
    end
  end

  @spec get_worksheet_info(non_neg_integer(), String.t()) :: map()
  defp get_worksheet_info(org_id, worksheet_code) do
    Repo.fetch_by(OrganizationData, %{
      organization_id: org_id,
      key: clean_worksheet_code(worksheet_code)
    })
    |> case do
      {:ok, data} ->
        data.json
        |> clean_map_keys()
        |> Map.put("worksheet_code", worksheet_code)
        |> Map.put("is_valid", true)
        |> Map.put("worksheet_code_label", worksheet_code)

      _ ->
        %{
          is_valid: false,
          message: "Worksheet code not found"
        }
    end
  end

  @spec get_school_id_info(non_neg_integer(), String.t()) :: map()
  defp get_school_id_info(org_id, school_id) do
    Repo.fetch_by(OrganizationData, %{
      organization_id: org_id,
      key: "school_ids_data"
    })
    |> case do
      {:ok, data} ->
        {key, value} =
          data.json
          |> Enum.find(fn {_k, v} ->
            v["userinputcorrectcode"]
            |> Glific.string_clean()
            |> String.equivalent?(school_id)
          end) || {nil, nil}

        %{
          is_valid: key != nil,
          info: value
        }

      _ ->
        %{
          is_valid: false,
          message: "Worksheet code not found"
        }
    end
  end

  @spec interactive_message_reflection_question(map(), String.t()) :: map()
  defp interactive_message_reflection_question(worksheet_code_info, langauge_label) do
    get_reflection_question_answer_count(worksheet_code_info, langauge_label)
    |> Map.merge(%{
      worksheet_code: worksheet_code_info["code"],
      langauge_label: langauge_label
    })
    |> Map.merge(worksheet_code_info)
  end

  @spec get_reflection_question_answer_count(map(), String.t()) :: map()
  defp get_reflection_question_answer_count(worksheet_code_info, langauge_label) do
    refelction_answers =
      case langauge_label do
        "English" ->
          %{
            valid_answers: worksheet_code_info["reflectionquestionvalidresponses"],
            correct_response: worksheet_code_info["reflectionquestionanswer"]
          }

        "Hindi" ->
          %{
            valid_answers: worksheet_code_info["reflectionquestionvalidresponseshindi"],
            correct_response: worksheet_code_info["reflectionquestionanswerhindi"]
          }

        "Kannada" ->
          %{
            valid_answers: worksheet_code_info["reflectionquestionvalidresponseskan"],
            correct_response: worksheet_code_info["reflectionquestionanswerkan"]
          }

        _ ->
          %{}
      end

    buttons =
      refelction_answers.valid_answers
      |> String.split("|")
      |> Enum.with_index()
      |> Enum.map(fn {answer, index} -> {"button_#{index + 1}", answer} end)
      |> Enum.into(%{})

    Map.merge(
      refelction_answers,
      %{
        buttons: buttons,
        button_count: length(Map.keys(buttons))
      }
    )
  end

  @spec clean_map_keys(map()) :: map()
  defp clean_map_keys(data) do
    data
    |> Enum.map(fn {k, v} -> {Glific.string_clean(k), v} end)
    |> Enum.into(%{})
  end

  @spec get_worksheet_msg(list(), Language.t()) :: String.t()
  defp get_worksheet_msg(completed_worksheets, language) do
    worksheet_count =
      completed_worksheets
      |> Enum.reduce(%{level_1: 0, level_2: 0, level_3: 0}, fn worksheet, acc ->
        cond do
          String.contains?(worksheet, "_prekg") ->
            Map.put(acc, :level_1, acc.level_1 + 1)

          String.contains?(worksheet, "_lkg") ->
            Map.put(acc, :level_2, acc.level_2 + 1)

          String.contains?(worksheet, "_ukg") ->
            Map.put(acc, :level_3, acc.level_3 + 1)

          true ->
            acc
        end
      end)

    do_get_worksheet_msg(worksheet_count, language.locale)
  end

  defp do_get_worksheet_msg(worksheet_count, "en") do
    """
    #{worksheet_count.level_1} worksheets for Level 1
    #{worksheet_count.level_2} worksheets for Level 2
    #{worksheet_count.level_3} worksheets for Level 3
    """
  end

  defp do_get_worksheet_msg(worksheet_count, "hi") do
    """
    स्तर 1 के #{worksheet_count.level_1} कार्यपत्रक
    स्तर 2 के #{worksheet_count.level_2} कार्यपत्रक
    स्तर 3 के #{worksheet_count.level_3} कार्यपत्रक
    """
  end

  defp do_get_worksheet_msg(worksheet_count, "kn") do
    """
    ಲೆವೆಲ್ 1 #{worksheet_count.level_1} ವರ್ಕ್‌ಶೀಟ್‌ಗಳು
    ಲೆವೆಲ್ 2 #{worksheet_count.level_2} ವರ್ಕ್‌ಶೀಟ್‌ಗಳು
    ಲೆವೆಲ್ 3 #{worksheet_count.level_3} ವರ್ಕ್‌ಶೀಟ್‌ಗಳು
    """
  end

  @spec get_completed_worksheets(map()) :: list()
  defp get_completed_worksheets(contact_fields) do
    completed_worksheet_codes =
      get_in(contact_fields, ["completed_worksheet_code", "value"]) || ""

    completed_worksheet_codes
    |> String.split(",", trim: true)
    |> Enum.uniq_by(&String.trim(&1))
  end

  @spec get_completed_helping_hands(map()) :: list()
  defp get_completed_helping_hands(contact_fields) do
    completed_helping_hands =
      get_in(contact_fields, ["completed_helping_hand_topic", "value"]) || ""

    completed_helping_hands
    |> String.split(",", trim: true)
    |> Enum.uniq_by(&String.trim(&1))
  end

  @spec clean_worksheet_code(String.t()) :: String.t()
  defp clean_worksheet_code(str) do
    code = Glific.string_clean(str)
    "worksheet_code_#{code}"
  end

  defp get_language(contact_id) do
    contact_id = Glific.parse_maybe_integer!(contact_id)

    contact =
      contact_id
      |> Contacts.get_contact!()
      |> Repo.preload([:language])

    contact.language
  end
end