defmodule Plausible.Workers.PurgeCDNCache do
  @moduledoc """
  Worker for purging CDN cache for tracker scripts on cloud.

  Uses Bunny CDN's API to purge cache by tag.
  Docs ref: https://docs.bunny.net/reference/pullzonepublic_purgecachepostbytag

  Note that purging by id "*" is equivalent to purging ALL cache.
  """

  use Oban.Worker,
    queue: :purge_cdn_cache,
    max_attempts: 5,
    # To avoid running into API rate limits, we:
    # - Schedule jobs with a delay
    # - Bump the scheduled time every time a new one is scheduled with the same args
    unique: [
      states: [:scheduled],
      fields: [:args]
    ]

  require Logger

  @impl Oban.Worker
  def perform(%Oban.Job{args: %{"id" => id}}) do
    pullzone_id = Application.get_env(:plausible, __MODULE__, [])[:pullzone_id]
    api_key = Application.get_env(:plausible, __MODULE__, [])[:api_key]

    purge_cache(id, pullzone_id, api_key)
  end

  @impl Oban.Worker
  def backoff(%Oban.Job{attempt: attempt}) do
    # Exponential backoff starting at 3 minutes
    trunc(:math.pow(2, attempt - 1) * 180)
  end

  defp purge_cache(id, pullzone_id, api_key) when is_nil(pullzone_id) or is_nil(api_key) do
    Logger.warning("Ignoring purge CDN cache for tracker script #{id}: Configuration missing")
    {:discard, "Configuration missing"}
  end

  defp purge_cache(id, pullzone_id, api_key) do
    options =
      [
        headers: [
          {"content-type", "application/json"},
          {"AccessKey", api_key}
        ],
        body: Jason.encode!(%{"CacheTag" => "tracker_script::#{id}"})
      ]
      |> Keyword.merge(Application.get_env(:plausible, __MODULE__)[:req_opts] || [])

    case Req.post("https://api.bunny.net/pullzone/#{pullzone_id}/purgeCache", options) do
      {:ok, %{status: 204}} ->
        Logger.notice("Successfully purged CDN cache for tracker script #{id}")
        {:ok, :success}

      {:ok, %{status: status}} ->
        Logger.warning(
          "Failed to purge CDN cache for tracker script #{id}: Unexpected status: #{status}"
        )

        {:error, "Unexpected status: #{status}"}

      {:error, reason} ->
        Logger.warning("Failed to purge CDN cache for tracker script #{id}: #{inspect(reason)}")
        {:error, reason}
    end
  end
end
