defmodule Aprsme.Cluster.PacketDistributorTest do
  use ExUnit.Case, async: false

  alias Aprsme.Cluster.LeaderElection
  alias Aprsme.Cluster.PacketDistributor

  @test_packet %{raw: "TEST>APRS:test packet", sender: "TEST"}

  setup do
    # Ensure cluster_enabled is false initially
    Application.put_env(:aprsme, :cluster_enabled, false)

    # Stop any running LeaderElection process
    if Process.whereis(LeaderElection) do
      try do
        GenServer.stop(LeaderElection, :normal, 100)
      catch
        :exit, _ -> :ok
      end
    end

    # Start LeaderElection in non-clustered mode
    {:ok, _} = LeaderElection.start_link([])
    # Sync with the GenServer so election (0ms in test config) has run.
    LeaderElection.leader?()

    on_exit(fn ->
      try do
        if Process.whereis(LeaderElection) do
          GenServer.stop(LeaderElection, :normal, 100)
        end
      catch
        :exit, _ -> :ok
      end

      # Reset cluster_enabled to false
      Application.put_env(:aprsme, :cluster_enabled, false)
    end)

    :ok
  end

  describe "distribute_packet/1" do
    test "does not distribute when cluster is disabled" do
      # cluster_enabled is false from setup
      PacketDistributor.subscribe()

      PacketDistributor.distribute_packet(@test_packet)

      refute_receive {:distributed_packet, _}, 10
    end

    test "distributes packet when cluster is enabled and node is leader" do
      Application.put_env(:aprsme, :cluster_enabled, true)

      PacketDistributor.subscribe()

      PacketDistributor.distribute_packet(@test_packet)

      assert_receive {:distributed_packet, packet}, 1000
      assert packet.raw == "TEST>APRS:test packet"
      assert packet.sender == "TEST"
    end
  end

  describe "subscribe/0" do
    test "subscribes to cluster:packets topic" do
      PacketDistributor.subscribe()

      Phoenix.PubSub.broadcast(
        Aprsme.PubSub,
        "cluster:packets",
        {:distributed_packet, @test_packet}
      )

      assert_receive {:distributed_packet, _}, 1000
    end
  end

  describe "handle_distributed_packet/1" do
    test "processes a distributed packet without crashing" do
      # SpatialPubSub is already running in test
      result = PacketDistributor.handle_distributed_packet({:distributed_packet, @test_packet})

      # Broadcasts to local clients and stores in PacketStore
      assert result == :ok
    end
  end

  describe "GenServer callbacks" do
    test "init/1 subscribes to the topic and returns empty state" do
      assert {:ok, %{}} = PacketDistributor.init([])
      # Callback subscribes the caller to cluster:packets.
      Phoenix.PubSub.broadcast(Aprsme.PubSub, "cluster:packets", :ping)
      assert_receive :ping, 1000
    end

    test "handle_info forwards distributed packets" do
      state = %{}

      assert {:noreply, ^state} =
               PacketDistributor.handle_info({:distributed_packet, @test_packet}, state)
    end

    test "terminate/2 unsubscribes from the topic and returns :ok" do
      # Subscribe first so we have something to unsubscribe.
      PacketDistributor.subscribe()
      assert :ok = PacketDistributor.terminate(:shutdown, %{})

      # After terminate, this process should no longer receive cluster:packets
      # broadcasts.
      Phoenix.PubSub.broadcast(Aprsme.PubSub, "cluster:packets", :should_not_arrive)
      refute_receive :should_not_arrive, 10
    end
  end
end
