lib/glific/misc/flags.ex

defmodule Glific.Flags do
  @moduledoc """
  Centralizing all the code we need to handle flags across Glific. For now, we'll
  also put operational code on flags here, as we figure out the right structure
  """

  use Publicist

  alias Glific.{
    Partners,
    Partners.Organization
  }

  @doc false
  @spec init(Organization.t()) :: nil
  def init(organization) do
    FunWithFlags.enable(
      :enable_out_of_office,
      for_actor: %{organization_id: organization.id}
    )

    out_of_office_update(organization)

    dialogflow(organization)
  end

  @spec business_day?(DateTime.t(), [integer]) :: boolean
  defp business_day?(time, days),
    do: (time |> DateTime.to_date() |> Date.day_of_week()) in days

  @spec office_hours?(DateTime.t(), [Time.t()]) :: boolean
  defp office_hours?(time, [start_time, end_time]) do
    time = DateTime.to_time(time)
    Time.compare(time, start_time) == :gt and Time.compare(time, end_time) == :lt
  end

  defp office_hours?(_time, []), do: false

  @spec enable_out_of_office(non_neg_integer) :: nil
  defp enable_out_of_office(organization_id) do
    # enable only if needed
    if !FunWithFlags.enabled?(
         :out_of_office_active,
         for: %{organization_id: organization_id}
       ),
       do:
         FunWithFlags.enable(
           :out_of_office_active,
           for_actor: %{organization_id: organization_id}
         )
  end

  @spec disable_out_of_office(non_neg_integer) :: nil
  defp disable_out_of_office(organization_id) do
    # disable only if needed
    if FunWithFlags.enabled?(
         :out_of_office_active,
         for: %{organization_id: organization_id}
       ),
       do:
         FunWithFlags.disable(
           :out_of_office_active,
           for_actor: %{organization_id: organization_id}
         )
  end

  @spec out_of_office_check(Organization.t()) :: nil
  defp out_of_office_check(organization) do
    if organization.out_of_office.enabled do
      {:ok, now} = organization.timezone |> DateTime.now()

      hours = organization.hours
      days = organization.days

      # check if current day and time is valid
      open? = business_day?(now, days) and office_hours?(now, hours)

      if open?,
        # we are operating now, so ensure out_of_office flag is disabled
        do: disable_out_of_office(organization.id),
        # we are closed now, enable out_of_office flow
        else: enable_out_of_office(organization.id)
    else
      # disable all out of office checks
      FunWithFlags.disable(
        :enable_out_of_office,
        for_actor: %{organization_id: organization.id}
      )

      FunWithFlags.disable(
        :out_of_office_active,
        for_actor: %{organization_id: organization.id}
      )
    end
  end

  @doc """
  Update the out of office flag, so we know if we should actually do any work
  """
  @spec out_of_office_update(Organization.t() | non_neg_integer) :: nil
  def out_of_office_update(organization) when is_integer(organization) do
    out_of_office_update(Partners.organization(organization))
  end

  def out_of_office_update(organization) do
    if(
      FunWithFlags.enabled?(
        :enable_out_of_office,
        for: %{organization_id: organization.id}
      ),
      do: out_of_office_check(organization),
      # lets make sure that out_of_office_active is disabled
      # if we dont want this functionality
      else: disable_out_of_office(organization.id)
    )
  end

  @doc """
  See if we have valid dialogflow credentials, if so, enable dialogflow
  else disable it
  """
  @spec dialogflow(Organization.t()) :: nil
  def dialogflow(organization) do
    organization.services["dialogflow"]
    |> case do
      nil ->
        FunWithFlags.disable(
          :dialogflow,
          for_actor: %{organization_id: organization.id}
        )

      _credential ->
        FunWithFlags.enable(
          :dialogflow,
          for_actor: %{organization_id: organization.id}
        )
    end

    nil
  end
end