defmodule SimpleshopTheme.Images.VariantCache do
  @moduledoc """
  Ensures all image variants exist on startup.

  This GenServer runs at startup and checks for:
  1. Database images with incomplete variants_status or missing disk files
  2. Mockup source files missing their generated variants

  For any images missing variants, it enqueues Oban jobs to regenerate them.
  """

  use GenServer
  require Logger

  alias SimpleshopTheme.Repo
  alias SimpleshopTheme.Media.Image, as: ImageSchema
  alias SimpleshopTheme.Images.{Optimizer, OptimizeWorker}
  alias SimpleshopTheme.Products
  alias SimpleshopTheme.Sync.ImageDownloadWorker
  import Ecto.Query

  defp mockup_dir, do: Application.app_dir(:simpleshop_theme, "priv/static/mockups")

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @impl true
  def init(_opts) do
    Task.start(fn -> ensure_all_variants() end)
    {:ok, %{}}
  end

  defp ensure_all_variants do
    Logger.info("[VariantCache] Checking image variant cache...")
    File.mkdir_p!(Optimizer.cache_dir())

    reset_stale_sync_status()
    ensure_database_image_variants()
    ensure_mockup_variants()
    ensure_product_image_downloads()
  end

  # Reset any provider connections stuck in "syncing" status from interrupted syncs
  defp reset_stale_sync_status do
    {count, _} = Products.reset_stale_sync_status()

    if count > 0 do
      Logger.info("[VariantCache] Reset #{count} stale sync status(es) to idle")
    end
  end

  defp ensure_database_image_variants do
    incomplete =
      ImageSchema
      |> where([i], i.variants_status != "complete" or is_nil(i.variants_status))
      |> where([i], i.is_svg == false)
      |> Repo.all()

    complete_missing =
      ImageSchema
      |> where([i], i.variants_status == "complete")
      |> where([i], i.is_svg == false)
      |> where([i], not is_nil(i.source_width))
      |> Repo.all()
      |> Enum.reject(fn img ->
        Optimizer.disk_variants_exist?(img.id, img.source_width)
      end)

    to_process = incomplete ++ complete_missing

    if to_process == [] do
      Logger.info("[VariantCache] All database image variants up to date")
    else
      Logger.info(
        "[VariantCache] Enqueueing #{length(to_process)} database images for processing"
      )

      Enum.each(to_process, fn image ->
        image
        |> ImageSchema.changeset(%{variants_status: "pending"})
        |> Repo.update!()

        OptimizeWorker.enqueue(image.id)
      end)
    end
  end

  defp ensure_mockup_variants do
    if File.dir?(mockup_dir()) do
      sources =
        Path.wildcard(Path.join(mockup_dir(), "*.webp"))
        |> Enum.reject(&is_variant?/1)

      missing = Enum.reject(sources, &mockup_variants_exist?/1)

      if missing == [] do
        Logger.info("[VariantCache] All mockup variants up to date")
      else
        Logger.info("[VariantCache] Enqueueing #{length(missing)} mockups for processing")
        Enum.each(missing, &OptimizeWorker.enqueue_mockup/1)
      end
    end
  end

  defp is_variant?(path) do
    basename = Path.basename(path) |> Path.rootname()
    String.match?(basename, ~r/-(400|800|1200|thumb)$/)
  end

  defp mockup_variants_exist?(source_path) do
    basename = Path.basename(source_path) |> Path.rootname()
    dir = Path.dirname(source_path)
    File.exists?(Path.join(dir, "#{basename}-800.webp"))
  end

  defp ensure_product_image_downloads do
    pending = Products.list_pending_downloads(limit: 500)

    if pending == [] do
      Logger.info("[VariantCache] All product images downloaded")
    else
      Logger.info("[VariantCache] Enqueueing #{length(pending)} product images for download")
      Enum.each(pending, fn image -> ImageDownloadWorker.enqueue(image.id) end)
    end
  end
end
