defmodule Dagger.Core.GraphQLClient do
  @moduledoc false

  alias Dagger.Core.GraphQL.Response

  @callback request(
              url :: String.t(),
              request_body :: iodata(),
              headers :: [tuple()],
              opts :: keyword()
            ) :: {:ok, status :: non_neg_integer(), response :: map()} | {:error, term()}

  @default_client Application.compile_env(:dagger, :client, Dagger.Core.GraphQLClient.Httpc)

  @doc """
  Perform a request to GraphQL server.
  """
  def request(url, session_token, query, variables, opts \\ []) do
    client = Keyword.get(opts, :client) || @default_client
    # TODO: change to json erlang standard library when support OTP 27 exclusively.
    json = Keyword.get(opts, :json_library, Jason)
    timeout = Keyword.get(opts, :timeout, :infinity)
    request = %{query: query, variables: variables}

    headers =
      []
      |> with_basic_auth(session_token)
      |> with_traceparent()

    with {:ok, request} <- json.encode(request),
         {:ok, status, result} <- client.request(url, request, headers, timeout: timeout),
         {:ok, map} <- json.decode(result) do
      response = Response.from_map(map)

      case status do
        200 -> {:ok, response}
        _ -> {:error, response}
      end
    else
      otherwise ->
        otherwise
    end
  end

  defp with_basic_auth(headers, session_token) when is_binary(session_token) do
    token = [session_token, ?:] |> IO.iodata_to_binary() |> Base.encode64()
    [{"authorization", ["Basic ", token]} | headers]
  end

  defp with_traceparent(headers, traceparent \\ System.get_env("TRACEPARENT")) do
    case traceparent do
      nil ->
        headers

      traceparent ->
        [{"traceparent", traceparent} | headers]
    end
  end
end
