defmodule ExViva.HTTP do
  alias ExViva.HTTP
  def request(request, opts \\ []) do
    request = preprocess_request(request, opts)

    {:ok, status, headers, client} = :hackney.request(request.method, request.url, request.headers, request.body, request.options)
    {:ok, body} = :hackney.body(client)

    {:ok, {status, headers, body} |> apply_transforms(request.response_handlers) }
  end

  defp apply_transforms(response, []), do: response
  defp apply_transforms(response, [ transform | transforms ]) do
    response
    |> apply_transforms(transforms)
    |> apply_transform(transform)
  end

  defp apply_transform({status, headers, body}, :normalize_headers) do
    {status, normalize_headers(headers), body}
  end

  defp apply_transform({status, headers, body}, :decode_json) do
    {:ok, json} = Jason.decode(body)
    {status, headers, json}
  end

  defp apply_transform({status, headers, body}, module) when is_atom(module) do
    module.handle_response({status, headers, body}, [])
  end

  defp normalize_headers(headers) do
    for {header, value} <- headers do
      {String.downcase(header), value}
    end
  end

  defp preprocess_request(request, opts) do
    %{ request |
      url: process_url(request.url, Keyword.get(opts, :base_url))
    }
    |> process_body()
  end

  defp process_body(%{body: {:json, data}} = request) do
    request
    |> HTTP.Request.put_header("Content-Type", "application/json")
    |> Map.put(:body, Jason.encode!(data))
  end

  defp process_body(%{body: ""} = request) do
    request
  end

  defp process_url("/" <> _, nil) do
    raise ArgumentError, "relative urls is not supported"
  end

  defp process_url("/" <> path, base_url) do
    "#{base_url}/#{path}"
  end

  defp process_url(url, _) do
    url
  end

end