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
  alias SimpleshopTheme.Images.Optimizer
  alias SimpleshopTheme.Images.OptimizeWorker

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

  For non-SVG images:
  - Converts to lossless WebP for storage (26-41% smaller than PNG)
  - Extracts source dimensions for responsive variant generation
  - Enqueues background job to generate optimized variants (AVIF, WebP, JPEG at multiple sizes)

  ## Examples

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

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

    case Repo.insert(ImageSchema.changeset(%ImageSchema{}, attrs)) do
      {:ok, image} ->
        # Enqueue background job for non-SVG images
        unless image.is_svg do
          OptimizeWorker.enqueue(image.id)
        end

        {:ok, image}

      error ->
        error
    end
  end

  # Prepares image attributes, converting to lossless WebP and extracting dimensions
  defp prepare_image_attrs(%{data: data, content_type: content_type} = attrs)
       when is_binary(data) do
    if is_svg?(content_type, attrs[:filename]) do
      attrs
    else
      case Optimizer.to_optimized_webp(data) do
        {:ok, webp_data, width, height} ->
          attrs
          |> Map.put(:data, webp_data)
          |> Map.put(:content_type, "image/webp")
          |> Map.put(:file_size, byte_size(webp_data))
          |> Map.put(:source_width, width)
          |> Map.put(:source_height, height)
          |> Map.put(:variants_status, "pending")

        {:error, _reason} ->
          # If conversion fails, store original image
          attrs
      end
    end
  end

  defp prepare_image_attrs(attrs), do: attrs

  defp is_svg?(content_type, filename) do
    content_type == "image/svg+xml" or
      String.ends_with?(filename || "", ".svg")
  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

  @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

  @doc """
  Gets the path to a thumbnail on disk.

  ## Examples

      iex> get_thumbnail_path("abc123-def456")
      "priv/static/image_cache/abc123-def456-thumb.jpg"

  """
  def get_thumbnail_path(image_id) do
    Path.join(Optimizer.cache_dir(), "#{image_id}-thumb.jpg")
  end

  @doc """
  Gets the path to a variant on disk.

  ## Examples

      iex> get_variant_path("abc123-def456", 800, :webp)
      "priv/static/image_cache/abc123-def456-800.webp"

  """
  def get_variant_path(image_id, width, format) do
    ext = Atom.to_string(format)
    Path.join(Optimizer.cache_dir(), "#{image_id}-#{width}.#{ext}")
  end
end
