defmodule AuthN.AuthenticationPlugMixin do
  @moduledoc ~S"""
  Allows to create a plug enforcing authentication for private routes.
  A user-defined module must `use` this module, making the user module a plug, and
  implement the `handle_authentication_error/2` behaviour. The
  `handle_authentication_error/2` callback receives a `Plug.Conn` struct and an
  atom identifying the set of routes that require authentication, and must return
  a `Plug.Conn` struct.
  Example:
    ```
    defmodule MyAppWeb.Plugs.EnsureAuthenticated do
      use AuthN.AuthenticationPlugMixin
      import Plug.Conn
      import Phoenix.Controller
      def handle_authentication_error(conn, :admin_routes),
        do: conn |> put_status(401) |> text("unauthenticated") |> halt()
    end
    ```
    `EnsureAuthenticated` is now a plug which can be used in the router:
    ```
    pipeline :ensure_admin_routes_authorized do
      plug MyAppWeb.Plugs.EnsureAuthenticated,
        resource: :admin_routes
    end
    scope "/admin", MyAppWeb, as: :admin do
      pipe_through [:browser, :ensure_admin_routes_authorized]
      # code
    end
    ```
  """
  @callback handle_authentication_error(Plug.Conn.t(), atom) :: Plug.Conn.t()
  defmacro __using__(_args) do
    this_module = __MODULE__
    quote do
      @behaviour unquote(this_module)
      def init(opts), do: opts
      def call(conn, opts) do
        if conn.assigns[:current_user] do
          conn
        else
          __MODULE__.handle_authentication_error(conn, Keyword.get(opts, :resource))
        end
      end
    end
  end
end