defmodule Pushex.Helpers do
  @moduledoc """
  Module containing helpers functions to use Pushex functionalities easily.
  """

  @doc """
  Sends a notification asynchrnously.
  The first argument can be a notification or a full request (i.e. a `Pushex.GCM.Request`)

  When using the first form, all the options passed will be passed to
  the request (e.g. `:priority`)

  The function raises an exception if the request cannot be executed,
  for example if a parameter is missing, or the requested application is not found.
  If the request is executed but fails, it should be handled in the response handler.

  ## Examples

      notification = %{title: "my title", body: "my body"}

      app    = %Pushex.GCM.App{name: "some name", auth_key: "my_auth_key"}
      reg_id = get_my_registration_id
      Pushex.push(notification, to: reg_id, with_app: app)

      # with default_app setup
      Pushex.push(notification, to: reg_id, using: :gcm)


      request = %Pushex.GCM.Request{app: app, notification: notification, to: reg_id}
      Pushex.push(request)
  """
  @spec send_notification(Pushex.GCM.request | Pushex.APNS.request | map, Keyword.t) :: reference
  def send_notification(request, opts \\ [])
  def send_notification(%Pushex.GCM.Request{} = request, _opts) do
    Pushex.Worker.send_notification(request)
  end
  def send_notification(%Pushex.APNS.Request{} = request, _opts) do
    Pushex.Worker.send_notification(request)
  end
  def send_notification(notification, opts) do
    if opts[:using] do
      do_send_notification(notification, opts[:using], opts)
    else
      case Keyword.get(opts, :with_app) do
        %Pushex.GCM.App{}  -> do_send_notification(notification, :gcm, opts)
        %Pushex.APNS.App{} -> do_send_notification(notification, :apns, opts)
        _                  -> raise ArgumentError, ":with_app must be a `Pushex.GCM.App` or `Pushex.APNS.App` when :using is not passed"
      end
    end
  end

  defp do_send_notification(notification, platform, opts) when platform in ["gcm", "apns"] do
    do_send_notification(notification, String.to_atom(platform), opts)
  end
  defp do_send_notification(notification, platform, opts) when platform in [:gcm, :apns] do
    {app, opts} = Keyword.pop(opts, :with_app)
    app = fetch_app(platform, app || default_app(platform))

    notification
    |> Pushex.Util.normalize_notification(platform)
    |> make_request(app, opts)
    |> Pushex.Worker.send_notification()
  end
  defp do_send_notification(_notification, platform, _opts) do
    raise ArgumentError, "#{inspect(platform)} is not a valid platform"
  end

  defp fetch_app(platform, nil) do
    raise ArgumentError, """
    you need to define a default app for the #{platform} in your config
    or to pass one explicitly with the :with_app parameter
    """
  end
  defp fetch_app(_platform, %Pushex.GCM.App{} = app), do: app
  defp fetch_app(_platform, %Pushex.APNS.App{} = app), do: app
  defp fetch_app(platform, app_name) when is_binary(app_name) or is_atom(app_name) do
    case Pushex.AppManager.find_app(platform, app_name) do
      nil -> raise Pushex.AppNotFoundError, platform: platform, name: app_name
      app -> app
    end
  end

  defp make_request(notification, %Pushex.GCM.App{} = app, opts) do
    Pushex.GCM.Request.create!(notification, app, opts)
  end
  defp make_request(notification, %Pushex.APNS.App{} = app, opts) do
    Pushex.APNS.Request.create!(notification, app, opts)
  end
  defp make_request(_notification, app, _opts) do
    raise ArgumentError, "application must be Pushex.GCM.App or Pushex.APNS.app, got #{inspect(app)}"
  end

  defp default_app(platform) do
    Application.get_env(:pushex, platform)[:default_app]
  end
end
