defmodule Glific.Flows.FlowResult do
  @moduledoc """
  Table which stores the flow results for each run of a contact through a flow
  """
  use Ecto.Schema
  import Ecto.Changeset

  alias __MODULE__

  alias Glific.{
    Contacts.Contact,
    Flows.Flow,
    Partners.Organization,
    Profiles.Profile,
    Repo
  }

  @required_fields [
    :contact_id,
    :flow_context_id,
    :flow_id,
    :flow_uuid,
    :flow_version,
    :organization_id
  ]
  @optional_fields [:results, :profile_id]

  @type t() :: %__MODULE__{
          __meta__: Ecto.Schema.Metadata.t(),
          id: non_neg_integer | nil,
          results: map() | nil,
          contact_id: non_neg_integer | nil,
          profile_id: non_neg_integer | nil,
          profile: Profile.t() | Ecto.Association.NotLoaded.t() | nil,
          contact: Contact.t() | Ecto.Association.NotLoaded.t() | nil,
          flow_context_id: non_neg_integer | nil,
          flow_id: non_neg_integer | nil,
          flow: Flow.t() | Ecto.Association.NotLoaded.t() | nil,
          flow_uuid: Ecto.UUID.t() | nil,
          flow_version: integer() | nil,
          organization_id: non_neg_integer | nil,
          organization: Organization.t() | Ecto.Association.NotLoaded.t() | nil,
          inserted_at: :utc_datetime | nil,
          updated_at: :utc_datetime | nil
        }

  schema "flow_results" do
    field :results, :map, default: %{}

    # Note that this is not a foreign key since contexts are cleaned up from the DB on a
    # periodic basis
    field :flow_context_id, :integer

    field :flow_version, :integer

    field :flow_uuid, Ecto.UUID

    belongs_to :contact, Contact
    belongs_to :flow, Flow
    belongs_to :organization, Organization
    belongs_to :profile, Profile

    timestamps(type: :utc_datetime_usec)
  end

  @doc """
  Standard changeset pattern we use for all data types
  """
  @spec changeset(FlowResult.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(:flow_context_id)
  end

  @doc false
  @spec upsert_flow_result(map()) :: {:ok, FlowResult.t()} | {:error, Ecto.Changeset.t()}
  def upsert_flow_result(attrs) do
    case Repo.get_by(FlowResult, %{flow_context_id: attrs.flow_context_id}) do
      nil -> %FlowResult{} |> changeset(attrs) |> Repo.insert()
      flow_result -> flow_result |> changeset(attrs) |> Repo.update()
    end
  end
end
