# frozen_string_literal: true

require "openssl"

# FastOG — official signer SDK (Ruby).
#
# Generates HMAC-SHA256 content-signed OG image URLs for the FastOG API.
# Zero dependencies (stdlib only). Runs on the CLIENT'S SERVER only — never
# expose the hmac secret to a browser.
#
# Contract (matches the FastOG server, App\Support\HmacSignature):
#   canonical = every query param except `s` (including `key`), keys sorted,
#               array values sorted, each pair `enc(key)=enc(value)` RFC-3986
#               encoded (unreserved-only), joined with "&"
#   message   = "GET/api/v1/og" + "\n" + canonical
#   signature = hex(HMAC-SHA256(message, secret))
module FastOg
  DEFAULT_BASE_URL = "https://fastog.com/api/v1/og"

  module_function

  # RFC-3986 percent-encode: keep only unreserved A-Za-z0-9-._~, uppercase hex.
  # Spaces -> %20 (NOT +), matching PHP rawurlencode byte-for-byte.
  def enc(value)
    value.to_s.gsub(/[^A-Za-z0-9\-._~]/) { |c| c.bytes.map { |b| "%%%02X" % b }.join }
  end
  private_class_method :enc

  # Canonical query string the server verifies.
  def canonicalize(params, api_key)
    pairs = params.merge("key" => api_key).flat_map do |k, v|
      values = v.is_a?(Array) ? v.sort : [v]
      values.map { |vv| "#{enc(k)}=#{enc(vv)}" }
    end
    pairs.sort.join("&")
  end

  # HMAC-SHA256 hex signature of `GET/api/v1/og\n<canonical>`.
  def sign(params, api_key, hmac_secret)
    OpenSSL::HMAC.hexdigest("SHA256", hmac_secret, "GET/api/v1/og\n#{canonicalize(params, api_key)}")
  end

  # Build a ready-to-use signed OG image URL.
  #
  # Array params are emitted as `key[0]=`/`key[1]=` bracket syntax so the FastOG
  # backend (PHP) decodes them as arrays (`key=a&key=b` would decode to a scalar
  # and fail the signature check).
  def signed_url(params, api_key, hmac_secret, base_url: DEFAULT_BASE_URL)
    all = params.merge("key" => api_key, "s" => sign(params, api_key, hmac_secret))
    pairs = all.flat_map do |k, v|
      if v.is_a?(Array)
        v.each_with_index.map { |vv, i| "#{enc("#{k}[#{i}]")}=#{enc(vv)}" }
      else
        ["#{enc(k)}=#{enc(v)}"]
      end
    end
    "#{base_url}?#{pairs.join("&")}"
  end
end
