defmodule WhoNeedHelp.Push do
  @moduledoc """
  Provider-neutral boundary and durable product-event enqueueing for remote push.

  The product deliberately remains disabled until a complete HTTP adapter
  configuration is supplied. Product events target a stable user identifier;
  the selected provider is responsible for resolving that identifier to one or
  more registered devices.
  """

  alias WhoNeedHelp.Push.DeliveryWorker

  @type notification :: %{
          required(:idempotency_key) => String.t(),
          required(:recipient) => String.t(),
          required(:title) => String.t(),
          required(:body) => String.t(),
          optional(:data) => map()
        }

  @type receipt :: %{id: String.t(), duplicate: boolean()}
  @type delivery_error :: :disabled | {:invalid_notification, atom()} | term()

  @callback deliver(notification(), keyword()) ::
              {:ok, receipt()} | {:error, delivery_error()}

  def enabled?, do: Application.get_env(:who_need_help, :push_product_enabled, false) == true

  def enqueue_request_accepted(assignment_id, request_id, requester_id) do
    enqueue(%{
      idempotency_key: "request-accepted:#{assignment_id}:#{requester_id}",
      recipient: user_recipient(requester_id),
      title: "A helper responded",
      body: "Open Who Need Help to see the request update.",
      data: %{
        "kind" => "request_accepted",
        "assignment_id" => assignment_id,
        "request_id" => request_id
      }
    })
  end

  def enqueue_message_created(message_id, assignment_id, request_id, recipient_id) do
    enqueue(%{
      idempotency_key: "message-created:#{message_id}:#{recipient_id}",
      recipient: user_recipient(recipient_id),
      title: "New message",
      body: "Open Who Need Help to read the conversation.",
      data: %{
        "kind" => "message_created",
        "assignment_id" => assignment_id,
        "message_id" => message_id,
        "request_id" => request_id
      }
    })
  end

  def enqueue(notification) do
    if enabled?() do
      notification
      |> DeliveryWorker.new()
      |> Oban.insert()
    else
      {:ok, :disabled}
    end
  end

  def deliver(notification, opts \\ []) do
    adapter =
      Keyword.get_lazy(opts, :adapter, fn ->
        Application.get_env(
          :who_need_help,
          :push_adapter,
          WhoNeedHelp.Push.DisabledAdapter
        )
      end)

    with :ok <- validate(notification) do
      adapter.deliver(notification, Keyword.delete(opts, :adapter))
    end
  end

  def delivery_options do
    Application.get_env(:who_need_help, :push_delivery_options, [])
  end

  defp user_recipient(user_id), do: "user:#{user_id}"

  defp validate(%{
         idempotency_key: idempotency_key,
         recipient: recipient,
         title: title,
         body: body
       })
       when is_binary(idempotency_key) and idempotency_key != "" and
              is_binary(recipient) and recipient != "" and
              is_binary(title) and title != "" and is_binary(body) and body != "" do
    :ok
  end

  defp validate(notification) when not is_map(notification),
    do: {:error, {:invalid_notification, :not_a_map}}

  defp validate(notification) do
    required = [:idempotency_key, :recipient, :title, :body]

    case Enum.find(required, fn key ->
           value = Map.get(notification, key)
           not (is_binary(value) and value != "")
         end) do
      nil -> :ok
      key -> {:error, {:invalid_notification, key}}
    end
  end
end
