defmodule SimpleshopTheme.Media do
  @moduledoc """
  The Media context for managing images and file uploads.
  """

  import Ecto.Query, warn: false
  alias SimpleshopTheme.Repo
  alias SimpleshopTheme.Media.Image, as: ImageSchema

  @thumbnail_size 200

  @doc """
  Uploads an image and stores it in the database.

  Automatically generates a thumbnail for non-SVG images if the Image library
  is available and working.

  ## Examples

      iex> upload_image(%{image_type: "logo", filename: "logo.png", ...})
      {:ok, %Image{}}

  """
  def upload_image(attrs) do
    attrs = maybe_generate_thumbnail(attrs)

    %ImageSchema{}
    |> ImageSchema.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Uploads an image from a LiveView upload entry.

  This handles consuming the upload and extracting metadata from the entry.

  ## Examples

      iex> upload_from_entry(socket, :logo_upload, fn path, entry -> ... end)
      {:ok, %Image{}}

  """
  def upload_from_entry(path, entry, image_type) do
    file_binary = File.read!(path)

    upload_image(%{
      image_type: image_type,
      filename: entry.client_name,
      content_type: entry.client_type,
      file_size: entry.client_size,
      data: file_binary
    })
  end

  defp maybe_generate_thumbnail(%{data: data, content_type: content_type} = attrs)
       when is_binary(data) do
    if String.starts_with?(content_type || "", "image/svg") do
      attrs
    else
      case generate_thumbnail(data) do
        {:ok, thumbnail_data} ->
          Map.put(attrs, :thumbnail_data, thumbnail_data)

        {:error, _reason} ->
          attrs
      end
    end
  end

  defp maybe_generate_thumbnail(attrs), do: attrs

  defp generate_thumbnail(image_data) do
    try do
      with {:ok, image} <- Image.from_binary(image_data),
           {:ok, thumbnail} <- Image.thumbnail(image, @thumbnail_size),
           {:ok, binary} <- Image.write(thumbnail, :memory, suffix: ".jpg") do
        {:ok, binary}
      end
    rescue
      _e ->
        {:error, :thumbnail_generation_failed}
    end
  end

  @doc """
  Gets a single image by ID.

  ## Examples

      iex> get_image(id)
      %Image{}

      iex> get_image("nonexistent")
      nil

  """
  def get_image(id) do
    Repo.get(ImageSchema, id)
  end

  @doc """
  Gets the current logo image.

  ## Examples

      iex> get_logo()
      %Image{}

  """
  def get_logo do
    Repo.one(from i in ImageSchema, where: i.image_type == "logo", order_by: [desc: i.inserted_at], limit: 1)
  end

  @doc """
  Gets the current header image.

  ## Examples

      iex> get_header()
      %Image{}

  """
  def get_header do
    Repo.one(from i in ImageSchema, where: i.image_type == "header", order_by: [desc: i.inserted_at], limit: 1)
  end

  @doc """
  Deletes an image.

  ## Examples

      iex> delete_image(image)
      {:ok, %Image{}}

  """
  def delete_image(%ImageSchema{} = image) do
    Repo.delete(image)
  end

  @doc """
  Lists all images of a specific type.

  ## Examples

      iex> list_images_by_type("logo")
      [%Image{}, ...]

  """
  def list_images_by_type(type) do
    Repo.all(from i in ImageSchema, where: i.image_type == ^type, order_by: [desc: i.inserted_at])
  end
end
