defmodule Aprsme.Telemetry.DatabaseMetricsTest do
  # Uses DataCase for a sandbox DB checkout — some metrics run live queries
  # against pg_stat_* views. async: false because we mutate :aprsme :env
  # in one test.
  use Aprsme.DataCase, async: false

  alias Aprsme.Telemetry.DatabaseMetrics

  defp attach_collector(event, tag \\ to_string(System.unique_integer([:positive]))) do
    test_pid = self()
    handler_id = "test-#{tag}-#{Enum.join(event, ",")}"

    :telemetry.attach(
      handler_id,
      event,
      fn ^event, measurements, metadata, _ ->
        send(test_pid, {:telemetry, event, measurements, metadata})
      end,
      nil
    )

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

  describe "collect_postgres_metrics/0 in :test env" do
    test "is a no-op returning :ok" do
      original = Application.get_env(:aprsme, :env)
      Application.put_env(:aprsme, :env, :test)

      try do
        assert DatabaseMetrics.collect_postgres_metrics() == :ok
      after
        Application.put_env(:aprsme, :env, original)
      end
    end
  end

  describe "collect_db_pool_metrics/0" do
    test "emits [:aprsme, :repo, :pool] telemetry with pool_size from config" do
      attach_collector([:aprsme, :repo, :pool])
      DatabaseMetrics.collect_db_pool_metrics()

      assert_receive {:telemetry, [:aprsme, :repo, :pool], measurements, _}, 1_000
      assert measurements.size == (Application.get_env(:aprsme, Aprsme.Repo)[:pool_size] || 10)
      assert map_size(measurements) == 1
    end
  end

  describe "collect_pgbouncer_metrics/0" do
    test "is a no-op returning :ok" do
      assert DatabaseMetrics.collect_pgbouncer_metrics() == :ok
    end
  end

  describe "collect_db_pool_metrics/0 when Repo is not running" do
    test "emits unavailable metrics when no Repo is registered" do
      # Stop the Repo for this test — we need to restart it afterward to avoid
      # breaking downstream tests.
      repo_pid = Process.whereis(Aprsme.Repo)

      if repo_pid do
        # Temporarily unregister without stopping to avoid disrupting other tests.
        :erlang.unregister(Aprsme.Repo)

        on_exit(fn ->
          if !Process.whereis(Aprsme.Repo) do
            true = Process.register(repo_pid, Aprsme.Repo)
          end
        end)
      end

      attach_collector([:aprsme, :repo, :pool])
      DatabaseMetrics.collect_db_pool_metrics()

      assert_receive {:telemetry, [:aprsme, :repo, :pool], measurements, _}, 1_000
      assert measurements.size == (Application.get_env(:aprsme, Aprsme.Repo)[:pool_size] || 10)
      assert map_size(measurements) == 1
    end
  end

  describe "to_number/1 and to_number/2 helpers (private — exercised through emit)" do
    # to_number/1 has 4 heads (nil, %Decimal{}, number, fallback). to_number/2 has
    # the same shape for the :integer overload. We exercise these via the public
    # functions that build telemetry payloads from query results.

    test "emits packets_table telemetry with Decimal values converted via to_number/1" do
      attach_collector([:aprsme, :postgres, :packets_table])

      original = Application.get_env(:aprsme, :env)
      Application.put_env(:aprsme, :env, :prod)

      try do
        DatabaseMetrics.collect_postgres_metrics()
      after
        Application.put_env(:aprsme, :env, original)
      end

      # Values should always be plain numbers, not Decimals.
      assert_receive {:telemetry, [:aprsme, :postgres, :packets_table], measurements, _}, 2_000
      Enum.each(measurements, fn {_k, v} -> assert is_number(v) end)
    end
  end

  describe "collect_postgres_metrics/0 outside :test env" do
    test "emits connections and packets_table telemetry events" do
      original = Application.get_env(:aprsme, :env)
      Application.put_env(:aprsme, :env, :prod)

      attach_collector([:aprsme, :postgres, :connections])
      attach_collector([:aprsme, :postgres, :packets_table])

      try do
        DatabaseMetrics.collect_postgres_metrics()
      after
        Application.put_env(:aprsme, :env, original)
      end

      assert_receive {:telemetry, [:aprsme, :postgres, :connections], conn_metrics, _}, 2_000
      assert Map.has_key?(conn_metrics, :total)
      assert Map.has_key?(conn_metrics, :active)
      assert Map.has_key?(conn_metrics, :idle)
    end
  end
end
