defmodule Recaptcha do
  @moduledoc """
  A module for verifying reCAPTCHA version 2.0 response strings.

  See the [documentation](https://developers.google.com/recaptcha/docs/verify)
  for more details.
  """

  alias Recaptcha.{Config, Http, Response}

  @http_client Application.get_env(:recaptcha, :http_client, Http)

  @doc """
  Verifies a reCAPTCHA response string.

  ## Options

    * `:timeout` - the timeout for the request (defaults to 5000ms)
    * `:secret`  - the secret key used by recaptcha (defaults to the secret
      provided in application config)
    * `:remote_ip` - the IP address of the user (optional and not set by default)

  ## Examples

      {:ok, api_response} = Recaptcha.verify("response_string")

  """
  @spec verify(String.t(), Keyword.t()) ::
            {:ok, Response.t()} | {:error, [atom]}
  def verify(response, options \\ []) do
    verification =
      @http_client.request_verification(
        request_body(response, options),
      options
      )

    case verification do
      {:error, errors} ->
        {:error, errors}

      {:ok, %{"success" => false, "error-codes" => errors}} ->
        {:error, Enum.map(errors, &atomise_api_error/1)}

      {:ok,
       %{"success" => true, "challenge_ts" => timestamp, "hostname" => host}} ->
        {:ok, %Response{challenge_ts: timestamp, hostname: host}}

      {:ok,
       %{"success" => false, "challenge_ts" => _timestamp, "hostname" => _host}} ->
        {:error, [:challenge_failed]}
    end
  end

  defp request_body(response, options) do
    body_options = Keyword.take(options, [:remote_ip, :secret])
    application_options = [secret: Config.get_env(:recaptcha, :secret)]

    # override application secret with options secret if it exists
    application_options
    |> Keyword.merge(body_options)
    |> Keyword.put(:response, response)
    |> URI.encode_query()
  end

  defp atomise_api_error(error) do
    # See why we are using `to_atom` here:
    # https://github.com/samueljseay/recaptcha/pull/28#issuecomment-313604733
    error
    |> String.replace("-", "_")
    |> String.to_atom()
  end
end
