defmodule AprsmeWeb.MapLive.PacketBatcherTest do
  use ExUnit.Case, async: true

  alias AprsmeWeb.MapLive.PacketBatcher

  setup do
    # Trap exits so linked batcher doesn't crash the test process
    Process.flag(:trap_exit, true)
    :ok
  end

  describe "start_link/1" do
    test "starts a batcher process" do
      {:ok, pid} = PacketBatcher.start_link(self())
      assert Process.alive?(pid)
      GenServer.stop(pid)
    end
  end

  describe "batching behavior" do
    test "rejects stale and duplicate timestamped packets for a sender" do
      {:ok, pid} = PacketBatcher.start_link(self())
      newer_at = ~U[2026-01-02 00:00:00Z]
      older_at = ~U[2026-01-01 00:00:00Z]

      PacketBatcher.add_packet(pid, %{sender: "ORDER-1", received_at: newer_at})
      PacketBatcher.add_packet(pid, %{sender: "ORDER-1", received_at: older_at})
      PacketBatcher.add_packet(pid, %{sender: "ORDER-1", received_at: newer_at})
      PacketBatcher.flush(pid)

      assert_receive {:packet_batch, [%{received_at: ^newer_at}]}, 1_000
      refute_receive {:packet_batch, _}, 100

      GenServer.stop(pid)
    end

    test "delivers packets in batch after timeout" do
      {:ok, pid} = PacketBatcher.start_link(self())

      PacketBatcher.add_packet(pid, %{sender: "TEST-1"})
      PacketBatcher.add_packet(pid, %{sender: "TEST-2"})

      # Should receive batch after the 100ms timeout
      assert_receive {:packet_batch, packets}, 1000
      assert length(packets) == 2
      assert Enum.at(packets, 0).sender == "TEST-1"
      assert Enum.at(packets, 1).sender == "TEST-2"

      GenServer.stop(pid)
    end

    test "delivers immediately when batch size reached" do
      {:ok, pid} = PacketBatcher.start_link(self())

      # Send 10 packets (batch size threshold)
      for i <- 1..10 do
        PacketBatcher.add_packet(pid, %{sender: "TEST-#{i}"})
      end

      # Should receive immediately (not waiting for timeout)
      assert_receive {:packet_batch, packets}, 1000
      assert length(packets) == 10

      GenServer.stop(pid)
    end

    test "flush/1 delivers buffered packets immediately" do
      {:ok, pid} = PacketBatcher.start_link(self())

      PacketBatcher.add_packet(pid, %{sender: "TEST-1"})
      PacketBatcher.flush(pid)

      assert_receive {:packet_batch, [%{sender: "TEST-1"}]}, 1000

      GenServer.stop(pid)
    end
  end

  describe "crash recovery" do
    test "batcher stops when parent dies" do
      # Start a temporary parent process that traps exits
      parent =
        spawn(fn ->
          Process.flag(:trap_exit, true)

          receive do
            :stop -> :ok
          end
        end)

      {:ok, batcher_pid} = PacketBatcher.start_link(parent)
      ref = Process.monitor(batcher_pid)

      # Kill the parent
      Process.exit(parent, :kill)

      # Batcher should stop
      assert_receive {:DOWN, ^ref, :process, ^batcher_pid, :normal}, 1000
    end
  end
end
