lib/glific/saas/onboard.ex

defmodule Glific.Saas.Onboard do
  @moduledoc """
  For now, we will build this on top of organization table, and have a group of helper functions
  here to manage global operations across all organizations.
  At some later point, we might decide to have a separate onboarding table and managment structure
  """
  alias Glific.{
    Communications.Mailer,
    Contacts.Contact,
    Mails.NewPartnerOnboardedMail,
    Partners,
    Partners.Billing,
    Partners.Organization,
    Repo,
    Saas.Queries
  }

  @doc """
  Setup all the tables and necessary values to onboard an organization
  """
  @spec setup(map()) :: map()
  def setup(params) do
    %{is_valid: true, messages: %{}}
    |> Queries.validate(params)
    |> Queries.setup(params)
    |> Queries.seed_data()
    |> format_results()
    |> notify_saas_team()
  end

  @spec add_map(map(), atom(), any()) :: map()
  defp add_map(map, _key, nil), do: map
  defp add_map(map, key, value), do: Map.put(map, key, value)

  @doc """
  Update the active and/or approved status of an organization
  """
  @spec status(non_neg_integer, atom()) :: Organization.t() | nil
  def status(update_organization_id, status) do
    changes =
      status
      |> organization_status(add_map(%{}, :status, status))

    {:ok, organization} =
      update_organization_id
      |> Partners.get_organization!()
      |> Partners.update_organization(changes)

    update_organization_billing(organization)
  end

  @spec organization_status(atom(), map()) :: map()
  defp organization_status(:active, changes) do
    changes
    |> add_map(:is_active, true)
    |> add_map(:is_approved, true)
  end

  defp organization_status(:approved, changes) do
    changes
    |> add_map(:is_active, false)
    |> add_map(:is_approved, true)
  end

  defp organization_status(_, changes) do
    changes
    |> add_map(:is_active, false)
    |> add_map(:is_approved, false)
  end

  @spec update_organization_billing(Organization.t()) :: Organization.t()
  defp update_organization_billing(%{is_active: false} = organization) do
    # putting organization id in process as this operation is used by glific_admin for other organizations
    Repo.put_process_state(organization.id)

    with billing <- Billing.get_billing(%{organization_id: organization.id}),
         false <- is_nil(billing),
         true <- billing.is_active do
      Billing.update_subscription(billing, organization)
    else
      _ -> organization
    end
  end

  defp update_organization_billing(organization), do: organization

  @doc """
  Delete an organization from the DB, ensure that the confirmed flag is set
  since this is a super destructive operation
  """
  @spec delete(non_neg_integer, boolean) ::
          {:ok, Organization.t()} | {:error, String.t() | Ecto.Changeset.t()}
  def delete(delete_organization_id, true) do
    organization = Partners.get_organization!(delete_organization_id)

    # ensure that the organization is not active, our last check before we
    # blow it away
    if organization.is_active do
      {:error, "Organization is still active"}
    else
      Partners.delete_organization(organization)
    end
  end

  def delete(_delete_organization_id, false), do: {:error, "Cannot delete organization"}

  @doc """
  Reset a few tables and fields for an organization, so they can get rid of all the test data and experiments.
  As dangerous as delete, so also needs confirmation
  """
  @spec reset(non_neg_integer, boolean) :: {:ok | :error, String.t()}
  def reset(reset_organization_id, true) do
    Queries.reset(reset_organization_id)
  end

  def reset(_, false), do: {:error, "Cannot reset organization data"}

  @spec format_results(map()) :: map()
  defp format_results(%{is_valid: true} = results) do
    results
    |> Map.put(:organization, Organization.to_minimal_map(results.organization))
    |> Map.put(:contact, Contact.to_minimal_map(results.contact))
    |> Map.put(:credential, "Gupshup secrets has been added.")
  end

  defp format_results(results), do: results

  @spec notify_saas_team(map()) :: map()
  defp notify_saas_team(%{is_valid: true} = results) do
    NewPartnerOnboardedMail.new_mail(results.organization)
    |> Mailer.send(%{
      category: "new_partner_onboarded",
      organization_id: results.organization.id
    })
    |> case do
      {:ok, _} ->
        results

      error ->
        Glific.log_error(
          "Error sending new partner onboarded email #{inspect(error)} for org: #{inspect(results)}"
        )
    end

    results
  end

  defp notify_saas_team(results), do: results
end