if Code.ensure_loaded?(Finch) do
  defmodule ExVCR.Adapter.Finch do
    @moduledoc """
    Provides adapter methods to mock Finch methods.
    """

    use ExVCR.Adapter

    alias ExVCR.Util

    defmacro __using__(_opts) do
      # do nothing
    end

    defdelegate convert_from_string(string), to: ExVCR.Adapter.Finch.Converter
    defdelegate convert_to_string(request, response), to: ExVCR.Adapter.Finch.Converter
    defdelegate parse_request_body(request_body), to: ExVCR.Adapter.Finch.Converter

    @doc """
    Returns the name of the mock target module.
    """
    def module_name do
      Finch
    end

    @doc """
    Returns list of the mock target methods with function name and callback.
    Implementation for global mock.
    """
    def target_methods() do
      [
        {:request, &ExVCR.Recorder.request([&1, &2])},
        {:request, &ExVCR.Recorder.request([&1, &2, &3])},
        {:request!, &(ExVCR.Recorder.request([&1, &2]) |> handle_response_for_request!())},
        {:request!, &(ExVCR.Recorder.request([&1, &2, &3]) |> handle_response_for_request!())}
      ]
    end

    @doc """
    Returns list of the mock target methods with function name and callback.
    """
    def target_methods(recorder) do
      [
        {:request, &ExVCR.Recorder.request(recorder, [&1, &2])},
        {:request, &ExVCR.Recorder.request(recorder, [&1, &2, &3])},
        {:request!, &(ExVCR.Recorder.request(recorder, [&1, &2]) |> handle_response_for_request!())},
        {:request!, &(ExVCR.Recorder.request(recorder, [&1, &2, &3]) |> handle_response_for_request!())}
      ]
    end

    @doc """
    Generate key for searching response.
    """
    def generate_keys_for_request(request) do
      req = Enum.fetch!(request, 0)
      url = Util.build_url(req.scheme, req.host, req.path, req.port, req.query)

      [url: url, method: String.downcase(req.method), request_body: req.body, headers: req.headers]
    end

    @doc """
    Callback from ExVCR.Handler when response is retrieved from the HTTP server.
    """
    def hook_response_from_server(response) do
      apply_filters(response)
    end

    @doc """
    Callback from ExVCR.Handler to get the response content tuple from the ExVCR.Response record.
    """
    def get_response_value_from_cache(response) do
      if response.type == "error" do
        {:error, response.body}
      else
        finch_response = %Finch.Response{
          status: response.status_code,
          headers: response.headers,
          body: response.body
        }

        {:ok, finch_response}
      end
    end

    defp apply_filters({:ok, %Finch.Response{} = response}) do
      filtered_response = apply_filters(response)
      {:ok, filtered_response}
    end

    defp apply_filters(%Finch.Response{} = response) do
      replaced_body = to_string(response.body) |> ExVCR.Filter.filter_sensitive_data()
      filtered_headers = ExVCR.Filter.remove_blacklisted_headers(response.headers)

      response
      |> Map.put(:body, replaced_body)
      |> Map.put(:headers, filtered_headers)
    end

    defp apply_filters({:error, reason}), do: {:error, reason}

    defp handle_response_for_request!({:ok, resp}), do: resp
    defp handle_response_for_request!({:error, error}), do: raise(error)
    defp handle_response_for_request!(resp), do: resp

    @doc """
    Default definitions for stub.
    """
    def default_stub_params(:headers), do: %{"content-type" => "text/html"}
    def default_stub_params(:status_code), do: 200
  end
else
  defmodule ExVCR.Adapter.Finch do
    def module_name, do: raise("Missing dependency: Finch")
    def target_methods, do: raise("Missing dependency: Finch")
  end
end
