defmodule Aprsme.Telemetry.DatabaseMetrics do
  @moduledoc """
  Collects database metrics from PostgreSQL and PgBouncer
  """
  require Logger

  def collect_db_pool_metrics do
    pool_size = Application.get_env(:aprsme, Aprsme.Repo)[:pool_size] || 10
    :telemetry.execute([:aprsme, :repo, :pool], %{size: pool_size}, %{})
  end

  def collect_postgres_metrics do
    do_collect_postgres_metrics(Application.get_env(:aprsme, :env))
  end

  # Skip database metrics collection in the test environment — the extra
  # pg_stat_* queries add test-suite noise and sometimes fail in CI sandboxes.
  defp do_collect_postgres_metrics(:test), do: :ok

  defp do_collect_postgres_metrics(_env) do
    case Process.whereis(Aprsme.Repo) do
      # Repo not started yet, skip metrics collection silently.
      nil -> :ok
      _pid -> collect_database_metrics()
    end
  end

  defp collect_database_metrics do
    collect_connection_stats()
    collect_packets_table_stats()
    collect_replication_lag()
  rescue
    e ->
      Logger.debug("Error collecting PostgreSQL metrics: #{inspect(e)}")
  end

  defp collect_connection_stats do
    case Aprsme.Repo.query("""
           SELECT
             count(*) as total,
             count(*) FILTER (WHERE state = 'active') as active,
             count(*) FILTER (WHERE state = 'idle') as idle,
             count(*) FILTER (WHERE state = 'idle in transaction') as idle_in_transaction,
             count(*) FILTER (WHERE wait_event_type IS NOT NULL) as waiting
           FROM pg_stat_activity
           WHERE datname = current_database()
         """) do
      {:ok, %{rows: [[total, active, idle, idle_in_tx, waiting]]}} ->
        :telemetry.execute(
          [:aprsme, :postgres, :connections],
          %{
            total: to_number(total),
            active: to_number(active),
            idle: to_number(idle),
            idle_in_transaction: to_number(idle_in_tx),
            waiting: to_number(waiting)
          },
          %{}
        )

      _ ->
        :ok
    end
  end

  defp collect_packets_table_stats do
    case Aprsme.Repo.query("""
           SELECT
             n_live_tup as live_tuples,
             n_dead_tup as dead_tuples,
             n_tup_ins as inserts,
             n_tup_upd as updates,
             n_tup_del as deletes
           FROM (
             SELECT
               SUM(n_live_tup) AS n_live_tup,
               SUM(n_dead_tup) AS n_dead_tup,
               SUM(n_tup_ins) AS n_tup_ins,
               SUM(n_tup_upd) AS n_tup_upd,
               SUM(n_tup_del) AS n_tup_del
             FROM pg_stat_user_tables
             WHERE relid = 'packets'::regclass
                OR relid IN (
                  SELECT inhrelid
                  FROM pg_inherits
                  WHERE inhparent = 'packets'::regclass
                )
           ) packet_stats
         """) do
      {:ok, %{rows: [[live, dead, ins, upd, del]]}} ->
        emit_packets_table_telemetry(live, dead, ins, upd, del)

      _ ->
        :ok
    end
  end

  defp collect_replication_lag do
    case Aprsme.Repo.query("""
           SELECT
             extract(epoch from (now() - pg_last_xact_replay_timestamp()))::int as lag_seconds
           WHERE pg_is_in_recovery()
         """) do
      {:ok, %{rows: [[lag]]}} when not is_nil(lag) ->
        :telemetry.execute([:aprsme, :postgres, :replication], %{lag_seconds: lag}, %{})

      _ ->
        :ok
    end
  end

  def collect_pgbouncer_metrics do
    # PgBouncer metrics would require a separate connection to PgBouncer's admin interface
    # For now, we'll skip these as they require additional setup
    :ok
  end

  defp emit_packets_table_telemetry(live, dead, ins, upd, del) do
    :telemetry.execute(
      [:aprsme, :postgres, :packets_table],
      %{
        live_tuples: to_number(live),
        dead_tuples: to_number(dead),
        total_inserts: to_number(ins),
        total_updates: to_number(upd),
        total_deletes: to_number(del)
      },
      %{}
    )
  end

  # Coerce Decimal/nil values to native Erlang numbers for :telemetry.execute
  defp to_number(nil), do: 0
  defp to_number(%Decimal{} = d), do: Decimal.to_float(d)
  defp to_number(n) when is_number(n), do: n
  defp to_number(_), do: 0
end
