defmodule MicrowavepropWeb.TelemetryTest do
  use Microwaveprop.DataCase, async: false

  alias Ecto.Adapters.SQL.Sandbox
  alias MicrowavepropWeb.Telemetry

  describe "metrics/0" do
    test "returns a flat list of Telemetry.Metrics structs" do
      metrics = Telemetry.metrics()

      assert is_list(metrics)
      assert Enum.any?(metrics)

      for m <- metrics do
        assert is_struct(m)
        # Every metric carries its event path as a list of atoms.
        assert is_list(m.event_name)
        assert Enum.all?(m.event_name, &is_atom/1)
      end
    end

    test "covers each of the metric categories defined in the module" do
      # The categories are private functions that build distinct event
      # name prefixes; check one canonical metric per category exists.
      names = Enum.map(Telemetry.metrics(), & &1.name)
      name_strings = Enum.map(names, &Enum.join(&1, "."))

      assert "phoenix.endpoint.stop.duration" in name_strings
      assert "microwaveprop.repo.query.total_time" in name_strings
      assert Enum.any?(name_strings, &String.starts_with?(&1, "vm."))
      assert Enum.any?(name_strings, &String.starts_with?(&1, "microwaveprop.oban."))
    end

    test "every duration metric carries a unit (no bare :native measurements leak)" do
      for m <- Telemetry.metrics(), String.ends_with?(to_string(List.last(m.name)), "duration") do
        assert m.unit != :unit, "#{inspect(m.name)} should declare a display unit"
      end
    end
  end

  describe "dispatch_oban_queue_depth/0" do
    test "emits one `oban.queue.depth` telemetry event per (queue, state) row" do
      test_pid = self()
      handler_id = {__MODULE__, :queue_depth_probe}

      :telemetry.attach(
        handler_id,
        [:microwaveprop, :oban, :queue, :depth],
        fn event, measurements, metadata, _cfg ->
          send(test_pid, {:telemetry, event, measurements, metadata})
        end,
        nil
      )

      try do
        assert :ok = Telemetry.dispatch_oban_queue_depth()

        # With an empty oban_jobs table in a fresh sandbox, no events fire —
        # the function still returns :ok and doesn't crash. If any rows
        # happen to be there, each must carry a queue+state tag.
        receive do
          {:telemetry, [:microwaveprop, :oban, :queue, :depth], %{count: n}, meta} ->
            assert is_integer(n)
            assert is_binary(meta.queue)
            assert Map.has_key?(meta, :state)
        after
          200 -> :ok
        end
      after
        :telemetry.detach(handler_id)
      end
    end

    test "swallows DB errors without crashing the poller" do
      # Manually close the sandbox connection so any DB query raises;
      # the poller must still return :ok (telemetry is best-effort).
      Sandbox.checkin(Microwaveprop.Repo)

      assert :ok = Telemetry.dispatch_oban_queue_depth()

      # Check a fresh connection back out so the async on_exit cleanup
      # doesn't trip over the closed sandbox.
      Sandbox.checkout(Microwaveprop.Repo)
    end
  end

  describe "start_link/1" do
    test "starts a named Supervisor under a fresh registry" do
      # Different process name to avoid colliding with the already-running
      # application supervisor.
      sup_name = :"telemetry_under_test_#{System.unique_integer([:positive])}"

      {:ok, pid} = Supervisor.start_link(Telemetry, :ok, name: sup_name)
      assert Process.alive?(pid)

      # Unlink before tearing down so the brutal-kill exit signal doesn't
      # propagate to the test process (which trap_exit defaults to off).
      Process.unlink(pid)
      Process.exit(pid, :kill)
    end
  end
end
