defmodule Microwaveprop.InstrumentTest do
  @moduledoc """
  Cover the `Instrument.span/3` wrapper directly. It's an unassuming
  5-line function but it's the emit point every call-site telemetry
  handler keys off, so regressions here are invisible at the call
  site but silently break metrics.
  """
  use ExUnit.Case, async: false

  alias Microwaveprop.Instrument

  setup do
    test_pid = self()

    handler_id = {__MODULE__, :instrument_handler, System.unique_integer([:positive])}

    :telemetry.attach_many(
      handler_id,
      [
        [:microwaveprop, :test, :start],
        [:microwaveprop, :test, :stop],
        [:microwaveprop, :test, :exception]
      ],
      fn event, measurements, metadata, _config ->
        send(test_pid, {:telemetry, event, measurements, metadata})
      end,
      nil
    )

    on_exit(fn -> :telemetry.detach(handler_id) end)

    :ok
  end

  test "wraps the function in a telemetry span and returns its value" do
    assert 42 == Instrument.span([:test], %{flavour: :unit}, fn -> 42 end)

    assert_receive {:telemetry, [:microwaveprop, :test, :start], %{}, %{flavour: :unit}}
    assert_receive {:telemetry, [:microwaveprop, :test, :stop], %{duration: dur}, stop_meta}

    assert dur >= 0
    assert stop_meta.flavour == :unit
    assert stop_meta.result == :ok
  end

  test "supports metadata default of %{}" do
    assert :ok == Instrument.span([:test], fn -> :ok end)

    assert_receive {:telemetry, [:microwaveprop, :test, :start], %{}, %{}}
    assert_receive {:telemetry, [:microwaveprop, :test, :stop], %{duration: _}, stop_meta}

    assert stop_meta.result == :ok
  end

  test "raising inside the span emits an exception event and re-raises" do
    assert_raise RuntimeError, "boom", fn ->
      Instrument.span([:test], %{}, fn -> raise "boom" end)
    end

    assert_receive {:telemetry, [:microwaveprop, :test, :start], _, _}
    assert_receive {:telemetry, [:microwaveprop, :test, :exception], %{duration: _}, exception_meta}
    # :telemetry.span/3 adds `kind`, `reason`, `stacktrace` keys on the
    # exception branch — assert the ones worth relying on.
    assert exception_meta.kind == :error
    assert %RuntimeError{message: "boom"} = exception_meta.reason
  end

  test "prefixes every event suffix with :microwaveprop" do
    # The whole point of the wrapper is that call sites pass [:foo, :bar]
    # and get telemetry rooted at [:microwaveprop, :foo, :bar]. Verify by
    # attaching a separate handler on a distinct suffix.
    test_pid = self()
    handler_id = {__MODULE__, :prefix_check, System.unique_integer([:positive])}

    :telemetry.attach(
      handler_id,
      [:microwaveprop, :prefix, :check, :stop],
      fn event, _m, _md, _c -> send(test_pid, {:evt, event}) end,
      nil
    )

    try do
      :ok = Instrument.span([:prefix, :check], fn -> :ok end)
      assert_receive {:evt, [:microwaveprop, :prefix, :check, :stop]}
    after
      :telemetry.detach(handler_id)
    end
  end
end
