defmodule Aprsme.PromEx.Plugins.AprsmeTest do
  use ExUnit.Case, async: true

  alias Aprsme.PromEx.Plugins.Aprsme, as: Plugin
  alias PromEx.MetricTypes.Event

  describe "event_metrics/1" do
    setup do
      groups = Plugin.event_metrics([])
      {:ok, groups: groups}
    end

    test "returns a list of Event structs" do
      groups = Plugin.event_metrics([])
      assert length(groups) == 9
      assert Enum.all?(groups, &match?(%Event{}, &1))
    end

    test "every Event has a unique group_name and a non-empty metrics list" do
      groups = Plugin.event_metrics([])
      group_names = Enum.map(groups, & &1.group_name)
      assert length(Enum.uniq(group_names)) == length(group_names)

      assert Enum.all?(groups, fn %Event{metrics: metrics} ->
               metrics != []
             end)
    end

    test "exposes a packet pipeline group", %{groups: groups} do
      group = find_group(groups, :aprsme_packet_pipeline_event_metrics)
      assert %Event{group_name: _, metrics: _} = group

      names = metric_names(group)
      assert [:aprsme, :packet_pipeline, :batch, :count] in names
      assert [:aprsme, :packet_pipeline, :batch, :success] in names
      assert [:aprsme, :packet_pipeline, :batch, :error] in names
      assert [:aprsme, :packet_pipeline, :batch, :duration_milliseconds] in names
    end

    test "exposes a producer backpressure group", %{groups: groups} do
      group = find_group(groups, :aprsme_packet_producer_event_metrics)
      assert %Event{group_name: _, metrics: _} = group
      names = metric_names(group)
      assert [:aprsme, :packet_producer, :backpressure, :count] in names
      assert [:aprsme, :packet_producer, :buffer_overflow, :total] in names
    end

    test "exposes a spatial pubsub group with all expected metrics", %{groups: groups} do
      group = find_group(groups, :aprsme_spatial_pubsub_event_metrics)
      assert %Event{group_name: _, metrics: _} = group
      names = metric_names(group)

      Enum.each(
        [
          [:aprsme, :spatial_pubsub, :clients, :count],
          [:aprsme, :spatial_pubsub, :clients, :grid_cells],
          [:aprsme, :spatial_pubsub, :clients, :avg_clients_per_cell],
          [:aprsme, :spatial_pubsub, :broadcasts, :total],
          [:aprsme, :spatial_pubsub, :broadcasts, :filtered],
          [:aprsme, :spatial_pubsub, :broadcasts, :packets],
          [:aprsme, :spatial_pubsub, :efficiency, :ratio],
          [:aprsme, :spatial_pubsub, :efficiency, :saved_broadcasts]
        ],
        fn n -> assert n in names end
      )
    end

    test "exposes the configured repo pool size without fabricated utilization", %{groups: groups} do
      group = find_group(groups, :aprsme_repo_pool_event_metrics)
      assert %Event{group_name: _, metrics: _} = group
      names = metric_names(group)

      assert [:aprsme, :repo, :pool, :size] in names
      assert length(names) == 1
    end

    test "exposes a postgres group covering db size, connections, table stats and replication", %{groups: groups} do
      group = find_group(groups, :aprsme_postgres_event_metrics)
      assert %Event{group_name: _, metrics: _} = group
      names = metric_names(group)

      assert [:aprsme, :postgres, :database, :size_bytes] in names
      assert [:aprsme, :postgres, :connections, :total] in names
      assert [:aprsme, :postgres, :connections, :active] in names
      assert [:aprsme, :postgres, :connections, :idle] in names
      assert [:aprsme, :postgres, :connections, :idle_in_transaction] in names
      assert [:aprsme, :postgres, :connections, :waiting] in names
      assert [:aprsme, :postgres, :packets_table, :live_tuples] in names
      assert [:aprsme, :postgres, :packets_table, :dead_tuples] in names
      assert [:aprsme, :postgres, :packets_table, :total_inserts] in names
      assert [:aprsme, :postgres, :packets_table, :total_updates] in names
      assert [:aprsme, :postgres, :packets_table, :total_deletes] in names
      assert [:aprsme, :postgres, :packets_table, :table_size_bytes] in names
      assert [:aprsme, :postgres, :packets_table, :indexes_size_bytes] in names
      assert [:aprsme, :postgres, :query_stats, :total_calls] in names
      assert [:aprsme, :postgres, :query_stats, :total_time_ms] in names
      assert [:aprsme, :postgres, :query_stats, :avg_time_ms] in names
      assert [:aprsme, :postgres, :query_stats, :max_time_ms] in names
      assert [:aprsme, :postgres, :replication, :lag_seconds] in names
    end

    test "exposes an api request metrics group", %{groups: groups} do
      group = find_group(groups, :aprsme_api_request_event_metrics)
      assert %Event{group_name: _, metrics: _} = group
      names = metric_names(group)

      assert [:aprsme, :api, :request, :total] in names
    end

    test "every metric carries a non-empty description" do
      groups = Plugin.event_metrics([])

      for group <- groups, metric <- group.metrics do
        assert byte_size(metric.description) > 0
        assert metric.description != ""
      end
    end

    test "the batch duration metric uses the configured millisecond buckets", %{groups: groups} do
      group = find_group(groups, :aprsme_packet_pipeline_event_metrics)

      dist =
        Enum.find(group.metrics, fn m ->
          m.name == [:aprsme, :packet_pipeline, :batch, :duration_milliseconds]
        end)

      assert %{name: _, unit: _, reporter_options: _} = dist
      assert dist.unit == :millisecond
      buckets = Keyword.get(dist.reporter_options, :buckets)
      assert buckets != []
      assert 10 in buckets
      assert 10_000 in buckets
    end
  end

  describe "default callbacks" do
    test "polling_metrics/1 returns []" do
      assert Plugin.polling_metrics([]) == []
    end

    test "manual_metrics/1 returns []" do
      assert Plugin.manual_metrics([]) == []
    end
  end

  defp find_group(groups, name), do: Enum.find(groups, &(&1.group_name == name))
  defp metric_names(%Event{metrics: metrics}), do: Enum.map(metrics, & &1.name)
end
