defmodule Parrhesia.IntegrationCase do
  @moduledoc false

  use ExUnit.CaseTemplate

  alias Ecto.Adapters.SQL.Sandbox
  alias ExUnit.Case
  alias Parrhesia.Repo
  alias Parrhesia.TestSupport.Runtime

  using opts do
    quote bind_quoted: [opts: opts] do
      use Case, async: Keyword.get(opts, :async, false)

      alias Ecto.Adapters.SQL.Sandbox
      alias Parrhesia.Repo

      @moduletag parrhesia_sandbox: Keyword.get(opts, :sandbox, false)
    end
  end

  setup tags do
    Runtime.ensure_started!()

    case tags[:parrhesia_sandbox] do
      false ->
        :ok

      true ->
        :ok = Sandbox.checkout(Repo)

      :shared ->
        # Delegate sandbox ownership to a dedicated process that outlives
        # the test process. ExUnit terminates start_supervised! children
        # (in reverse start order) before running on_exit callbacks. With
        # the test process as sandbox owner, the connection dies the
        # moment the test process exits — supervised children that make
        # DB calls during their shutdown sequence would hit a dead
        # connection. The separate owner stays alive through the entire
        # supervised-child shutdown, and is only stopped in on_exit
        # (which runs afterwards).
        test_pid = self()

        owner =
          spawn(fn ->
            :ok = Sandbox.checkout(Repo)
            send(test_pid, {:sandbox_ready, self()})

            receive do
              :stop ->
                Sandbox.checkin(Repo)

                # Allow the pool to process the checkin before this process
                # exits, so Postgrex does not see a dead client and log a
                # spurious disconnect error.
                Process.sleep(50)
            end
          end)

        receive do
          {:sandbox_ready, ^owner} -> :ok
        after
          5_000 -> raise "Sandbox owner checkout timed out"
        end

        Sandbox.mode(Repo, {:shared, owner})

        on_exit(fn ->
          ref = Process.monitor(owner)
          send(owner, :stop)

          receive do
            {:DOWN, ^ref, :process, ^owner, _reason} -> :ok
          after
            5_000 -> :ok
          end

          Sandbox.mode(Repo, :manual)
        end)
    end

    :ok
  end
end
