defmodule Elasticsearch.API.HTTP do
  @moduledoc """
  A "real" HTTP implementation of `Elasticsearch.API`.
  """

  @behaviour Elasticsearch.API

  @impl true
  def request(config, method, url, data, opts) do
    method
    |> HTTPoison.request(
      process_url(url, config),
      process_request_body(data, config),
      headers(config),
      opts ++ Map.get(config, :default_options, [])
    )
    |> process_response(config)
  end

  # Respect absolute URLs if passed
  defp process_url("http" <> _rest = url, _config) do
    url
  end

  # On relative urls, prepend the configured base URL
  defp process_url(url, config) do
    Path.join(config.url, url)
  end

  # Converts the request body into JSON, unless it has already
  # been converted. If the data is empty, sends ""
  defp process_request_body(data, _config) when is_binary(data) do
    data
  end

  defp process_request_body(data, config) when is_map(data) and data != %{} do
    json_library(config).encode!(data)
  end

  defp process_request_body(_data, _config) do
    ""
  end

  # Converts the response body string from JSON into a map, if it looks like it
  # is actually JSON
  defp process_response({:ok, %{body: body} = response}, config) do
    body =
      cond do
        json?(body) -> json_library(config).decode!(body)
        true -> body
      end

    {:ok, %{response | body: body}}
  end

  defp process_response(response, _config) do
    response
  end

  defp json?(str) when is_binary(str) do
    str =~ ~r/^\{/ || str =~ ~r/^\[/
  end

  # Produces request headers for the request, based on the configuration
  defp headers(config) do
    headers = [{"Content-Type", "application/json"}] ++ Map.get(config, :default_headers, [])

    credentials = http_basic_credentials(config)

    if credentials do
      [{"Authorization", "Basic #{credentials}"} | headers]
    else
      headers
    end
  end

  defp http_basic_credentials(%{username: username, password: password}) do
    Base.encode64("#{username}:#{password}")
  end

  defp http_basic_credentials(_config) do
    nil
  end

  defp json_library(%{json_library: json_library}) do
    json_library
  end

  defp json_library(_config) do
    Poison
  end
end
