defmodule WhoNeedHelp.PushProductTest do
  use WhoNeedHelp.DataCase, async: false
  use Oban.Testing, repo: WhoNeedHelp.Repo

  import WhoNeedHelp.AccountsFixtures

  alias WhoNeedHelp.{Catalog, Help, Messaging, Push}
  alias WhoNeedHelp.Push.DeliveryWorker

  defmodule RecordingAdapter do
    @behaviour Push

    @impl true
    def deliver(notification, opts) do
      send(Keyword.fetch!(opts, :test_pid), {:push_delivered, notification})
      {:ok, %{id: "local-receipt", duplicate: false}}
    end
  end

  setup do
    keys = [:push_product_enabled, :push_adapter, :push_delivery_options]
    previous = Map.new(keys, &{&1, Application.fetch_env(:who_need_help, &1)})

    on_exit(fn ->
      Enum.each(previous, fn
        {key, {:ok, value}} -> Application.put_env(:who_need_help, key, value)
        {key, :error} -> Application.delete_env(:who_need_help, key)
      end)
    end)

    category = Catalog.seed_defaults()
    requester = user_fixture(display_name: "Push requester")
    helper = user_fixture(display_name: "Push helper")

    attrs = %{
      "title" => "Reserved medicine",
      "description" => "Pickup is ready.",
      "pickup_instructions" => "Ask for the reserved order.",
      "location_label" => "Central district",
      "latitude" => "50.4501",
      "longitude" => "30.5234",
      "urgency" => "now",
      "location_visibility" => "approximate_public",
      "structured_data" => %{"pickup_status" => "reserved"},
      "expires_at" => DateTime.utc_now(:second) |> DateTime.add(3, :hour),
      "category_id" => category.id,
      "safety_confirmed" => true
    }

    {:ok,
     requester: requester,
     helper: helper,
     requester_scope: user_scope_fixture(requester),
     helper_scope: user_scope_fixture(helper),
     request_attrs: attrs}
  end

  test "acceptance and chat atomically enqueue privacy-safe recipient events", context do
    Application.put_env(:who_need_help, :push_product_enabled, true)

    {:ok, request} = Help.create_request(context.requester_scope, context.request_attrs)
    {:ok, assignment} = Help.accept_request(context.helper_scope, request.id)

    assert_enqueued(
      worker: DeliveryWorker,
      queue: :push,
      args: %{
        "idempotency_key" => "request-accepted:#{assignment.id}:#{context.requester.id}",
        "recipient" => "user:#{context.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
        }
      }
    )

    {:ok, message} =
      Messaging.send_message(context.requester_scope, assignment, %{"body" => "Thank you"})

    assert_enqueued(
      worker: DeliveryWorker,
      queue: :push,
      args: %{
        "idempotency_key" => "message-created:#{message.id}:#{context.helper.id}",
        "recipient" => "user:#{context.helper.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
        }
      }
    )

    refute Enum.any?(all_enqueued(worker: DeliveryWorker), fn job ->
             inspect(job.args) =~ "Thank you"
           end)
  end

  test "event idempotency key creates only one retained Oban job", context do
    Application.put_env(:who_need_help, :push_product_enabled, true)

    key = "request-accepted:#{Ecto.UUID.generate()}:#{context.requester.id}"
    [_, assignment_id, requester_id] = String.split(key, ":")
    request_id = Ecto.UUID.generate()

    assert {:ok, first} =
             Push.enqueue_request_accepted(assignment_id, request_id, requester_id)

    assert {:ok, replay} =
             Push.enqueue_request_accepted(assignment_id, request_id, requester_id)

    assert replay.conflict?
    assert replay.id == first.id
    assert 1 == length(all_enqueued(worker: DeliveryWorker, args: %{"idempotency_key" => key}))
  end

  test "disabled product boundary leaves domain events job-free", context do
    Application.put_env(:who_need_help, :push_product_enabled, false)

    {:ok, request} = Help.create_request(context.requester_scope, context.request_attrs)
    {:ok, assignment} = Help.accept_request(context.helper_scope, request.id)

    {:ok, _message} =
      Messaging.send_message(context.helper_scope, assignment, %{"body" => "On my way"})

    assert [] == all_enqueued(worker: DeliveryWorker)
  end

  test "delivery worker reconstructs and sends the persisted notification" do
    Application.put_env(:who_need_help, :push_adapter, RecordingAdapter)
    Application.put_env(:who_need_help, :push_delivery_options, test_pid: self())

    args = %{
      "idempotency_key" => "message-created:event:user",
      "recipient" => "user:recipient",
      "title" => "New message",
      "body" => "Open Who Need Help to read the conversation.",
      "data" => %{"kind" => "message_created"}
    }

    assert :ok = perform_job(DeliveryWorker, args)

    assert_received {:push_delivered,
                     %{
                       idempotency_key: "message-created:event:user",
                       recipient: "user:recipient",
                       data: %{"kind" => "message_created"}
                     }}
  end
end
