if Code.ensure_loaded?(Plug) do
  defmodule GlificWeb.Plug do
    @moduledoc """
    This file and the below files have been "borrowed and modified" from triplex: https://github.com/ateliware/triplex
    The original copyright and license - MIT belong to the authors and contributors of Triplex

      * plug.ex
      * ensure_plug.ex
      * ensure_plug_config.ex
      * subdomain_plug.ex
      * subdomain_plug_config.ex
      * param_plug.ex
      * param_plug_config.ex

    This module have some basic functions for our triplex plugs.

    The plugs we have for now are:

    - `GlificWeb.ParamPlug` - loads the organization from a body or query param
    - `GlificWeb.SubdomainPlug` - loads the organization from the url subdomain
    - `GlificWeb.EnsurePlug` - ensures the current organization is loaded and halts if not
    """

    alias Plug.Conn

    require Logger

    @raw_organization_assign :raw_organization_name

    @doc """
    Global unauthorized error handler
    """
    @spec send_error(Conn.t()) :: Conn.t()
    def send_error(conn) do
      conn
      |> Conn.put_status(403)
      |> Conn.send_resp(403, "Unauthorized")
      |> Conn.halt()
    end

    @doc """
    Puts the given `organization` as an assign on the given `conn`, but only if the
    organization is not reserved.

    The `config` map/struct must have:

    - `organization_handler`: function to handle the organization param. Its return will
    be used as the organization.
    - `assign`: the name of the assign where we must save the organization.
    """
    @spec put_organization(Conn.t(), any(), map()) :: Conn.t()
    def put_organization(conn, organization, config) do
      if conn.assigns[config.assign] do
        conn
      else
        conn = Conn.assign(conn, @raw_organization_assign, organization)
        organization_id = organization_handler(organization, config.organization_handler)

        cond do
          GlificWeb.Tenants.reserved_organization?(organization) ->
            conn

          organization_id == 0 ->
            Logger.info("Halting on failure to retrieve #{organization}")
            send_error(conn)

          true ->
            Glific.Repo.put_organization_id(organization_id)
            Conn.assign(conn, config.assign, organization_id)
        end
      end
    end

    @doc """
    Ensure the organization is loaded, and if not, halts the `conn`.

    The `config` map/struct must have:

    - `assign`: the name of the assign where we must save the organization.
    """
    @spec ensure_organization(Conn.t(), map()) :: Conn.t()
    def ensure_organization(conn, config) do
      if loaded_organization = conn.assigns[config.assign] do
        callback(conn, loaded_organization, config.callback)
      else
        conn
        |> callback(conn.assigns[@raw_organization_assign], config.failure_callback)
        |> send_error()
      end
    end

    @spec organization_handler(any(), any()) :: any()
    defp organization_handler(organization, nil),
      do: organization

    defp organization_handler(organization, handler) when is_function(handler),
      do: handler.(organization)

    defp callback(conn, _, nil),
      do: conn

    defp callback(conn, organization, callback) when is_function(callback),
      do: callback.(conn, organization)
  end
end
