lib/blockchain_api.ex

defmodule HeliumElixir.BlockchainApi do
  use HTTPoison.Base
  require HTTPoison.Retry
  alias HTTPoison.Response
  alias HTTPoison.Retry

  @base_url "https://helium-api.stakejoy.com/v1"

  def process_request_url(url) do
    @base_url <> url
  end

  def post(url, body \\ %{}) do
    case post(url, Jason.encode!(body), [{"Content-Type", "application/json"}]) do
      {:ok, %Response{body: body}} ->
        {:ok, body |> Jason.decode!()}

      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, reason}
    end
  end

  def get_json(url, params \\ %{}) do
    start()

    case get(url, [], timeout: 20_000, recv_timeout: 20_000, params: params)
         |> Retry.autoretry(
           max_attempts: 2,
           wait: 15000,
           include_404s: true,
           retry_unknown_errors: true
         ) do
      {:ok, %Response{body: body, status_code: status_code}} ->
        case status_code do
          200 ->
            {:ok, body |> Jason.decode!()}

          _ ->
            {:error, status_code}
        end

      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, reason}
    end
  end

  # paginates through all entries of a provided url
  def paginate_all(url, acc \\ [], cursor \\ nil) do
    case get_json(url, %{cursor: cursor}) do
      {:ok, %{"data" => data, "cursor" => next_cursor}} ->
        paginate_all(url, acc ++ data, next_cursor)

      {:ok, %{"data" => data}} ->
        {:ok, acc ++ data}

      {:error, error} ->
        {:error, error}
    end
  end

  # paginates through all entries of a provided url until count
  def take(url, count, acc \\ [], cursor \\ nil) do
    case get_json(url, %{cursor: cursor}) do
      {:ok, %{"data" => data, "cursor" => next_cursor}} ->
        next_data = acc ++ data
        acc_count = length(next_data)

        if(acc_count >= count || is_nil(next_cursor)) do
          {:ok, %{"data" => next_data, "cursor" => next_cursor}}
        else
          take(url, count, next_data, next_cursor)
        end

      {:ok, %{"data" => data}} ->
        {:ok, %{"data" => acc ++ data, "cursor" => ""}}

      {:error, error} ->
        len = length(acc)

        case len do
          0 ->
            {:error, error}

          _ ->
            {:ok, %{"data" => acc, "cursor" => cursor}}
        end
    end
  end

  def each_page(url, fun, cursor \\ nil) do
    case get_json(url, %{cursor: cursor}) do
      {:ok, %{"data" => data, "cursor" => next_cursor}} ->
        fun.(data)
        each_page(url, fun, next_cursor)

      {:ok, %{"data" => data}} ->
        fun.(data)
        :ok

      {:error, error} ->
        {:error, error}
    end
  end

  # paginates through all entries of a provided url, while fun returns true
  def paginate_while(url, fun, params \\ %{}, acc \\ [], cursor \\ nil) do
    case get_json(url, Map.merge(params, %{cursor: cursor})) do
      {:ok, %{"data" => data, "cursor" => next_cursor}} ->
        filtered_data = data |> Enum.take_while(fn d -> fun.(d) end)

        if length(filtered_data) == length(data) do
          paginate_while(url, fun, params, acc ++ filtered_data, next_cursor)
        else
          {:ok, acc ++ filtered_data}
        end

      {:ok, %{"data" => data}} ->
        filtered_data = data |> Enum.take_while(fn d -> fun.(d) end)
        {:ok, acc ++ filtered_data}

      {:error, error} ->
        {:error, error}
    end
  end

  # returns the first entry that fun evaluates true for
  def paginate_find(url, fun, params \\ %{}, cursor \\ nil) do
    case get_json(url, Map.merge(params, %{cursor: cursor})) do
      {:ok, %{"data" => data, "cursor" => next_cursor}} ->
        case Enum.find(data, fun) do
          nil ->
            paginate_find(url, fun, params, next_cursor)

          found ->
            {:ok, found}
        end

      {:ok, %{"data" => data}} ->
        case Enum.find(data, fun) do
          nil ->
            {:error, :not_found}

          found ->
            {:ok, found}
        end

      {:error, error} ->
        {:error, error}
    end
  end
end