defmodule ExVCR.Adapter.IBrowse.Converter do
  @moduledoc """
  Provides helpers to mock :ibrowse methods.
  """

  use ExVCR.Converter

  defp string_to_response(string) do
    response = Enum.map(string, fn {x, y} -> {String.to_atom(x), y} end)
    response = struct(ExVCR.Response, response)

    response =
      if response.status_code do
        %{response | status_code: Integer.to_charlist(response.status_code)}
      else
        response
      end

    response =
      if response.type == "error" do
        body = string_to_error_reason(response.body)
        %{response | body: body}
      else
        response
      end

    response =
      if is_map(response.headers) do
        headers =
          response.headers
          |> Map.to_list()
          |> Enum.map(fn {k, v} -> {to_charlist(k), to_charlist(v)} end)

        %{response | headers: headers}
      else
        response
      end

    response
  end

  defp string_to_error_reason([reason, details]), do: {String.to_atom(reason), binary_to_tuple(details)}
  defp string_to_error_reason([reason]), do: String.to_atom(reason)

  defp request_to_string([url, headers, method]), do: request_to_string([url, headers, method, [], []])
  defp request_to_string([url, headers, method, body]), do: request_to_string([url, headers, method, body, []])

  defp request_to_string([url, headers, method, body, options]),
    do: request_to_string([url, headers, method, body, options, 5000])

  defp request_to_string([url, headers, method, body, options, _timeout]) do
    %ExVCR.Request{
      url: parse_url(url),
      headers: parse_headers(headers),
      method: Atom.to_string(method),
      body: parse_request_body(body),
      options: parse_options(sanitize_options(options))
    }
  end

  # If option value is tuple, make it as list, for encoding as json.
  defp sanitize_options(options) do
    Enum.map(options, fn {key, value} ->
      if is_tuple(value) do
        {key, Tuple.to_list(value)}
      else
        {key, value}
      end
    end)
  end

  defp response_to_string({:ok, status_code, headers, body}) do
    %ExVCR.Response{
      type: "ok",
      status_code: List.to_integer(status_code),
      headers: parse_headers(headers),
      body: to_string(body)
    }
  end

  defp response_to_string({:error, reason}) do
    %ExVCR.Response{
      type: "error",
      body: error_reason_to_string(reason)
    }
  end

  defp error_reason_to_string({reason, details}), do: [Atom.to_string(reason), tuple_to_binary(details)]
  defp error_reason_to_string(reason), do: [Atom.to_string(reason)]

  defp tuple_to_binary(tuple) do
    Enum.map(Tuple.to_list(tuple), fn x ->
      if is_atom(x), do: Atom.to_string(x), else: x
    end)
  end

  defp binary_to_tuple(list) do
    Enum.map(list, fn x ->
      if is_binary(x), do: String.to_atom(x), else: x
    end)
    |> List.to_tuple()
  end
end
