defmodule Microwaveprop.Weather.IemRateLimiterTest do
  use ExUnit.Case, async: false

  alias Microwaveprop.Weather.IemRateLimiter

  # Each test starts an isolated limiter under a unique name so the
  # app-level IemRateLimiter (started by the application supervisor
  # with interval_ms: 0) doesn't conflict.
  defp start_limiter(interval_ms) do
    name = :"iem_rate_limiter_test_#{System.unique_integer([:positive])}"
    start_supervised!({IemRateLimiter, interval_ms: interval_ms, name: name})
    name
  end

  describe "acquire/1" do
    test "first call returns immediately when the limiter is idle" do
      name = start_limiter(50)

      {elapsed_ms, :ok} = :timer.tc(fn -> IemRateLimiter.acquire(name) end, :millisecond)

      # Allow a generous upper bound for scheduling jitter; the point is that
      # no rate-limit sleep was applied on the first call.
      assert elapsed_ms < 20
    end

    test "serial calls are spaced at least interval_ms apart" do
      name = start_limiter(50)

      {elapsed_ms, _} =
        :timer.tc(
          fn ->
            IemRateLimiter.acquire(name)
            IemRateLimiter.acquire(name)
            IemRateLimiter.acquire(name)
          end,
          :millisecond
        )

      # Three acquires at 50ms gap → floor at 100ms (gap after call 1 and 2).
      assert elapsed_ms >= 100
    end

    test "concurrent callers each get a non-overlapping slot" do
      name = start_limiter(50)
      parent = self()

      for _ <- 1..4 do
        spawn(fn ->
          IemRateLimiter.acquire(name)
          send(parent, {:acquired, System.monotonic_time(:millisecond)})
        end)
      end

      timestamps =
        for _ <- 1..4 do
          assert_receive {:acquired, ts}, 500
          ts
        end

      sorted = Enum.sort(timestamps)
      gaps = sorted |> Enum.zip(tl(sorted)) |> Enum.map(fn {a, b} -> b - a end)
      min_gap = Enum.min(gaps)
      # Allow 5ms scheduler jitter on the lower bound.
      assert min_gap >= 45
    end

    test "interval_ms: 0 is a no-op" do
      name = start_limiter(0)

      {elapsed_ms, _} =
        :timer.tc(
          fn ->
            for _ <- 1..10, do: IemRateLimiter.acquire(name)
          end,
          :millisecond
        )

      assert elapsed_ms < 20
    end
  end
end
