lib/ex_catalog.ex

defmodule ExCatalog do
  @moduledoc """
  Documentation for `ExCatalog`.
  """
  @repo ExCatalog.Config.repo()

  alias ExCatalog.Category
  alias ExCatalog.Product

  @doc """
  List version.

  ## Examples

      iex> ExCatalog.version()


  """
  @version Mix.Project.config()[:version]
  def version, do: @version

  @doc """
  List all the categories (the index).

  ## Examples

      iex> ExCatalog.index(25)


  """
  def index(limit \\ 25, metadata \\ nil, cursor \\ nil, deleted \\ false) do
    import Ecto.Query
    import Ecto.SoftDelete.Query

    query =
      case(deleted) do
        false ->
          from(ExCatalog.Category,
            preload: [:parent_category],
            preload: [:image]
          )

        true ->
          from(ExCatalog.Category,
            preload: [:parent_category],
            preload: [:image]
          )
          |> with_undeleted
      end

    case cursor do
      :before ->
        @repo.paginate(
          query,
          before: metadata.before,
          include_total_count: true,
          cursor_fields: [:inserted_at, :id],
          limit: limit
        )

      :after ->
        @repo.paginate(
          query,
          after: metadata.after,
          include_total_count: true,
          cursor_fields: [:inserted_at, :id],
          limit: limit
        )

      _ ->
        @repo.paginate(
          query,
          include_total_count: true,
          cursor_fields: [:inserted_at, :id],
          limit: limit
        )
    end
  end

  @doc """
  List all products with preloads and optional currency conversion..

  ## Examples

      iex> ExCatalog.products(25)
      iex> ExCatalog.products(25,:USD)


  """
  def products(limit \\ 25, currency \\ :USD, deleted \\ false, owner_id \\ nil) do
    products(limit, nil, nil, currency, deleted, owner_id)
  end

  def products(limit \\ 25, metadata, cursor, currency, deleted, owner_id) do
    import Ecto.Query
    import Ecto.SoftDelete.Query

    query =
      case(owner_id) do
        nil ->
          case(deleted) do
            false ->
              from(ExCatalog.Product,
                preload: [:variations],
                preload: [:categories],
                preload: [:metas],
                preload: [:primary_image],
                preload: [:images],
                preload: [:videos]
              )

            true ->
              from(ExCatalog.Product,
                preload: [:variations],
                preload: [:categories],
                preload: [:metas],
                preload: [:primary_image],
                preload: [:images],
                preload: [:videos]
              )
              |> with_undeleted
          end

        _ ->
          case(deleted) do
            false ->
              from(ExCatalog.Product,
                where: [owner_id: ^owner_id],
                preload: [:variations],
                preload: [:categories],
                preload: [:metas],
                preload: [:primary_image],
                preload: [:images],
                preload: [:videos]
              )

            true ->
              from(ExCatalog.Product,
                where: [owner_id: ^owner_id],
                preload: [:variations],
                preload: [:categories],
                preload: [:metas],
                preload: [:primary_image],
                preload: [:images],
                preload: [:videos]
              )
              |> with_undeleted
          end
      end

    reply =
      case cursor do
        :before ->
          @repo.paginate(
            query,
            before: metadata.before,
            include_total_count: true,
            cursor_fields: [:inserted_at, :id],
            limit: limit
          )

        :after ->
          @repo.paginate(
            query,
            after: metadata.after,
            include_total_count: true,
            cursor_fields: [:inserted_at, :id],
            limit: limit
          )

        _ ->
          @repo.paginate(
            query,
            include_total_count: true,
            cursor_fields: [:inserted_at, :id],
            limit: limit
          )
      end

    case currency do
      nil ->
        reply

      _ ->
        {p, metadata} = reply

        modified =
          Enum.map(p, fn x ->
            {:ok, price} = ExCatalog.Currencies.convert(x.price, currency)
            %{x | price: price}
          end)

        {modified, metadata}
    end
  end

  @doc """
  List product with preloads by sku and optional currency conversion.

  ## Examples

      iex> ExCatalog.product("2242", :USD)


  """
  def product(sku, currency \\ nil, deleted \\ false) do
    import Ecto.Query
    import Ecto.SoftDelete.Query

    query =
      case(deleted) do
        false ->
          from(ExCatalog.Product,
            where: [sku: ^sku],
            preload: [:variations],
            preload: [:categories],
            preload: [:metas],
            preload: [:primary_image],
            preload: [:images],
            preload: [:videos]
          )

        true ->
          from(ExCatalog.Product,
            where: [sku: ^sku],
            preload: [:variations],
            preload: [:categories],
            preload: [:metas],
            preload: [:primary_image],
            preload: [:images],
            preload: [:videos]
          )
          |> with_undeleted
      end

    [reply] = @repo.all(query)

    case currency do
      nil ->
        reply

      _ ->
        {:ok, price} = ExCatalog.Currencies.convert(reply.price, currency)
        %{reply | price: price}
    end
  end

  @doc """
  List product with preloads by category and optional currency conversion.

  ## Examples

      iex> ExCatalog.products_by_category("test_category", "2242", :USD, false, order_by: :sku)


  """
  def products_by_category(slug, limit \\ 25, currency \\ :USD, deleted \\ false, opts \\ []) do
    products_by_category(slug, limit, nil, nil, currency, deleted, opts)
  end

  def products_by_category(slug, limit, metadata, cursor, currency, deleted, opts) do
    category = @repo.get_by(%Category{}, slug: slug)

    import Ecto.Query
    import Ecto.SoftDelete.Query

    order = Keyword.get(opts, :order) || :desc
    by = Keyword.get(opts, :order_by) || :updated_at
    order_by = [{order, by}]

    query =
      case(deleted) do
        false ->
          from(ExCatalog.Product,
            where: [category_id: ^category.id],
            order_by: ^order_by,
            preload: [:variations],
            preload: [:categories],
            preload: [:metas],
            preload: [:primary_image],
            preload: [:images],
            preload: [:videos]
          )

        true ->
          from(ExCatalog.Product,
            where: [category_id: ^category.id],
            preload: [:variations],
            preload: [:categories],
            preload: [:metas],
            preload: [:primary_image],
            preload: [:images],
            preload: [:videos]
          )
          |> with_undeleted
      end

    reply =
      case cursor do
        :before ->
          @repo.paginate(
            query,
            before: metadata.before,
            include_total_count: true,
            cursor_fields: [:inserted_at, :id],
            limit: limit
          )

        :after ->
          @repo.paginate(
            query,
            after: metadata.after,
            include_total_count: true,
            cursor_fields: [:inserted_at, :id],
            limit: limit
          )

        _ ->
          @repo.paginate(
            query,
            include_total_count: true,
            cursor_fields: [:inserted_at, :id],
            limit: limit
          )
      end

    case currency do
      nil ->
        reply

      _ ->
        {data, meta} = reply

        modified =
          Enum.map(data, fn x ->
            {:ok, price} = ExCatalog.Currencies.convert(x.price, currency)
            %{x | price: price}
          end)

        {modified, meta}
    end
  end

  @doc """
  Change the status active or disabled, this controls which products the user can see, (by default only the active products are displayed)

  ## Examples

      iex> id = "1111-2222-3333-4444"
      iex> ExCatalog.active(:category, id)
      iex> ExCatalog.active(:product, id)


  """
  def active(type, id) do
    import Ecto.Query
    import Ecto.SoftDelete.Query

    query =
      case type do
        :category ->
          from(c in Category, where: c.id == ^id, select: c)
          |> with_undeleted

        _ ->
          from(p in Product, where: p.id == ^id, select: p)
          |> with_undeleted
      end

    case Keyword.has_key?(@repo.__info__(:functions), :soft_restore!) do
      true ->
        @repo.one!(query)
        |> @repo.soft_restore!()

      false ->
        {:error, "Not Available"}
    end
  end

  def active(type, id, false) do
    import Ecto.Query
    import Ecto.SoftDelete.Query

    case type do
      :category ->
        @repo.get_by!(Category, id: id)

        from(c in Category, where: c.id == ^id, select: c)
        |> @repo.soft_delete!()

      _ ->
        from(p in Product, where: p.id == ^id, select: p)
        |> @repo.soft_delete!()
    end
  end
end