defmodule Glific.Flows.FlowCount do
  @moduledoc """
  The flow count object
  """
  use Ecto.Schema
  import Ecto.Changeset

  import Ecto.Query, warn: false
  alias __MODULE__

  alias Glific.{
    Flows.Flow,
    Partners.Organization,
    Repo
  }

  @required_fields [:uuid, :flow_id, :type, :flow_uuid, :organization_id]
  @optional_fields [:destination_uuid, :recent_messages, :count]

  @type t() :: %__MODULE__{
          __meta__: Ecto.Schema.Metadata.t(),
          id: non_neg_integer | nil,
          uuid: Ecto.UUID.t() | nil,
          flow_id: non_neg_integer | nil,
          flow_uuid: Ecto.UUID.t() | nil,
          flow: Flow.t() | Ecto.Association.NotLoaded.t() | nil,
          organization_id: non_neg_integer | nil,
          organization: Organization.t() | Ecto.Association.NotLoaded.t() | nil,
          type: String.t() | nil,
          count: integer() | nil,
          destination_uuid: Ecto.UUID.t() | nil,
          recent_messages: [map()] | nil,
          inserted_at: :utc_datetime | nil,
          updated_at: :utc_datetime | nil
        }

  schema "flow_counts" do
    field :uuid, Ecto.UUID
    field :flow_uuid, Ecto.UUID
    belongs_to :flow, Flow
    belongs_to :organization, Organization
    field :type, :string
    field :count, :integer
    field :destination_uuid, Ecto.UUID
    field :recent_messages, {:array, :map}, default: []

    timestamps(type: :utc_datetime)
  end

  @doc """
  Standard changeset pattern we use for all data types
  """
  @spec changeset(FlowCount.t(), map()) :: Ecto.Changeset.t()
  def changeset(flow_revision, attrs) do
    flow_revision
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
    |> unique_constraint([:uuid, :flow_id, :type])
    |> foreign_key_constraint(:flow_id)
  end

  @doc """
  Get a list of flow count
  """
  @spec get_flow_count_list(Ecto.UUID.t()) :: :error | list()
  def get_flow_count_list(nil), do: []

  def get_flow_count_list(flow_uuid) do
    FlowCount
    |> where([fc], fc.flow_uuid == ^flow_uuid)
    |> Repo.all()
  end

  @doc """
  Create flow count
  """
  @spec create_flow_count(map()) :: {:ok, FlowCount.t()} | {:error, Ecto.Changeset.t()}
  def create_flow_count(attrs) do
    %FlowCount{}
    |> FlowCount.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Update flow count
  """
  @spec update_flow_count(FlowCount.t(), map()) ::
          {:ok, FlowCount.t()} | {:error, Ecto.Changeset.t()}
  def update_flow_count(%FlowCount{} = flow_count, attrs) do
    flow_count
    |> FlowCount.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Upsert flow count
  """
  @spec upsert_flow_count(map()) :: :error | FlowCount.t()
  def upsert_flow_count(%{flow_uuid: nil} = _attrs), do: :error

  def upsert_flow_count(attrs) do
    case Repo.fetch_by(FlowCount, %{uuid: attrs.uuid, flow_id: attrs.flow_id, type: attrs.type}) do
      {:ok, flowcount} ->
        update_flow_count(
          flowcount,
          Map.merge(attrs, %{
            count: flowcount.count + attrs.count,
            recent_messages: Enum.take(attrs.recent_messages ++ flowcount.recent_messages, 5)
          })
        )

      {:error, _} ->
        create_flow_count(attrs)
    end
  end

  @doc """
  Resets the flow count for a given flow
  """
  @spec reset_flow_count(non_neg_integer) :: any
  def reset_flow_count(flow_id) do
    FlowCount
    |> where([fc], fc.flow_id == ^flow_id)
    |> Repo.delete_all()
  end
end
