defmodule SimpleshopTheme.Products.Product do
  @moduledoc """
  Schema for products synced from POD providers.

  Products are uniquely identified by the combination of
  provider_connection_id and provider_product_id.
  """

  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  @statuses ~w(active draft archived)

  schema "products" do
    field :provider_product_id, :string
    field :title, :string
    field :description, :string
    field :slug, :string
    field :status, :string, default: "active"
    field :visible, :boolean, default: true
    field :category, :string
    field :provider_data, :map, default: %{}
    field :checksum, :string

    belongs_to :provider_connection, SimpleshopTheme.Products.ProviderConnection
    has_many :images, SimpleshopTheme.Products.ProductImage
    has_many :variants, SimpleshopTheme.Products.ProductVariant

    timestamps(type: :utc_datetime)
  end

  @doc """
  Returns the list of valid product statuses.
  """
  def statuses, do: @statuses

  @doc """
  Changeset for creating or updating a product.
  """
  def changeset(product, attrs) do
    product
    |> cast(attrs, [
      :provider_connection_id,
      :provider_product_id,
      :title,
      :description,
      :slug,
      :status,
      :visible,
      :category,
      :provider_data,
      :checksum
    ])
    |> generate_slug_if_missing()
    |> validate_required([:provider_connection_id, :provider_product_id, :title, :slug])
    |> validate_inclusion(:status, @statuses)
    |> unique_constraint(:slug)
    |> unique_constraint([:provider_connection_id, :provider_product_id])
  end

  @doc """
  Generates a checksum from provider data for detecting changes.
  """
  def compute_checksum(provider_data) when is_map(provider_data) do
    provider_data
    |> Jason.encode!()
    |> then(&:crypto.hash(:sha256, &1))
    |> Base.encode16(case: :lower)
    |> binary_part(0, 16)
  end

  def compute_checksum(_), do: nil

  defp generate_slug_if_missing(changeset) do
    case get_field(changeset, :slug) do
      nil ->
        title = get_change(changeset, :title) || get_field(changeset, :title)

        if title do
          put_change(changeset, :slug, Slug.slugify(title))
        else
          changeset
        end

      _ ->
        changeset
    end
  end
end

defmodule Slug do
  @moduledoc false

  def slugify(nil), do: nil

  def slugify(string) when is_binary(string) do
    string
    |> String.downcase()
    |> String.replace(~r/[^\w\s-]/, "")
    |> String.replace(~r/\s+/, "-")
    |> String.replace(~r/-+/, "-")
    |> String.trim("-")
  end
end
