defmodule Aprsme.BroadcastTaskSupervisorTest do
  use ExUnit.Case, async: true

  import ExUnit.CaptureLog

  alias Aprsme.BroadcastTaskSupervisor

  setup do
    # Ensure the supervisor is started (it should be from application)
    # Create a test topic for PubSub
    test_topic = "test_topic_#{System.unique_integer()}"
    Phoenix.PubSub.subscribe(Aprsme.PubSub, test_topic)

    {:ok, test_topic: test_topic}
  end

  describe "broadcast_async/3" do
    test "returns {:ok, pid} for fire-and-forget execution", %{test_topic: test_topic} do
      topics = [test_topic]
      message = {:test_message, "Hello from broadcast"}

      assert {:ok, pid} = BroadcastTaskSupervisor.broadcast_async(topics, message)
      assert is_pid(pid)
    end

    test "broadcasts messages to multiple topics asynchronously", %{test_topic: test_topic} do
      # Create additional test topics
      topic2 = "test_topic_2_#{System.unique_integer()}"
      topic3 = "test_topic_3_#{System.unique_integer()}"

      Phoenix.PubSub.subscribe(Aprsme.PubSub, topic2)
      Phoenix.PubSub.subscribe(Aprsme.PubSub, topic3)

      topics = [test_topic, topic2, topic3]
      message = {:test_message, "Hello from broadcast"}

      # Broadcast to all topics (fire-and-forget)
      {:ok, _pid} = BroadcastTaskSupervisor.broadcast_async(topics, message)

      # Should receive the message on all topics
      assert_receive {:test_message, "Hello from broadcast"}, 1000
      assert_receive {:test_message, "Hello from broadcast"}, 1000
      assert_receive {:test_message, "Hello from broadcast"}, 1000
    end

    test "does not send task ref or DOWN messages to calling process", %{test_topic: test_topic} do
      topics = [test_topic]
      message = {:test_ref_check, "checking refs"}

      {:ok, _pid} = BroadcastTaskSupervisor.broadcast_async(topics, message)

      # Wait for the broadcast to complete
      assert_receive {:test_ref_check, "checking refs"}, 1000

      # Ensure no {ref, result} or {:DOWN, ...} messages leaked into our mailbox
      refute_receive {_ref, _result}, 100
      refute_receive {:DOWN, _ref, :process, _pid, _reason}, 100
    end

    test "handles large topic lists efficiently", %{test_topic: test_topic} do
      # Create 100 topics
      topics =
        for i <- 1..100 do
          topic = "bulk_test_#{i}_#{System.unique_integer()}"
          Phoenix.PubSub.subscribe(Aprsme.PubSub, topic)
          topic
        end

      # Add our test topic
      topics = [test_topic | topics]

      message = {:bulk_test, "Bulk broadcast"}

      # Measure time
      start_time = System.monotonic_time(:millisecond)
      {:ok, _pid} = BroadcastTaskSupervisor.broadcast_async(topics, message)

      # Wait for delivery to our subscribed topic
      assert_receive {:bulk_test, "Bulk broadcast"}, 5000
      end_time = System.monotonic_time(:millisecond)

      # Should complete in a reasonable time (under 1 second for 101 topics)
      assert end_time - start_time < 1000
    end
  end

  describe "broadcast_one_async/3" do
    test "broadcasts a single message asynchronously", %{test_topic: test_topic} do
      message = {:single_test, "Single broadcast"}

      {:ok, _pid} = BroadcastTaskSupervisor.broadcast_one_async(test_topic, message)

      # Give it a moment to process
      Process.sleep(10)

      assert_receive {:single_test, "Single broadcast"}
    end
  end

  describe "async_execute/1" do
    test "executes arbitrary functions asynchronously" do
      test_pid = self()

      BroadcastTaskSupervisor.async_execute(fn ->
        send(test_pid, :function_executed)
      end)

      assert_receive :function_executed, 1000
    end

    test "handles errors in async functions gracefully" do
      # Capture logs to suppress the expected error output
      capture_log(fn ->
        # This should not crash the supervisor
        {:ok, _pid} =
          BroadcastTaskSupervisor.async_execute(fn ->
            raise "Test error"
          end)

        # Give it time to fail - reduced from 50ms to 10ms
        Process.sleep(10)
      end)

      # Supervisor should still be running
      stats = BroadcastTaskSupervisor.get_stats()
      assert Map.has_key?(stats, :active_tasks)
    end
  end

  describe "get_stats/0" do
    test "returns statistics about the broadcast pool" do
      stats = BroadcastTaskSupervisor.get_stats()

      assert Map.has_key?(stats, :active_tasks)
      assert Map.has_key?(stats, :pool_size)
      assert Map.has_key?(stats, :scheduler_usage)

      assert is_integer(stats.active_tasks)
      assert stats.active_tasks >= 0

      assert is_integer(stats.pool_size)
      assert stats.pool_size > 0

      assert is_float(stats.scheduler_usage)
      assert stats.scheduler_usage >= 0.0
    end

    test "scheduler_usage with non-test env exercises the live scheduler sampling" do
      # Mutating :aprsme :env into :prod for one call lets get_stats hit the
      # live :scheduler.utilization branch. Sample seconds=0 uses the
      # instant sample-pair API rather than blocking for a full second.
      original_env = Application.get_env(:aprsme, :env)
      original_seconds = Application.get_env(:aprsme, :scheduler_sample_seconds)
      Application.put_env(:aprsme, :env, :prod)
      Application.put_env(:aprsme, :scheduler_sample_seconds, 0)

      try do
        stats = BroadcastTaskSupervisor.get_stats()
        assert is_float(stats.scheduler_usage)
        assert stats.scheduler_usage >= 0.0
      after
        Application.put_env(:aprsme, :env, original_env)

        if original_seconds,
          do: Application.put_env(:aprsme, :scheduler_sample_seconds, original_seconds),
          else: Application.delete_env(:aprsme, :scheduler_sample_seconds)
      end
    end
  end

  describe "performance under load" do
    test "handles concurrent broadcasts efficiently" do
      # Create test topics
      topics =
        for i <- 1..10 do
          "perf_test_#{i}"
        end

      # Subscribe to only the first topic
      Phoenix.PubSub.subscribe(Aprsme.PubSub, List.first(topics))

      # Launch 100 concurrent broadcasts (fire-and-forget)
      for i <- 1..100 do
        message = {:perf_test, i}
        {:ok, _pid} = BroadcastTaskSupervisor.broadcast_async(topics, message)
      end

      # Each topic should receive 100 messages
      message_count = receive_all_messages()

      # We subscribed to the first topic, so we should get 100 messages
      assert message_count == 100
    end
  end

  # Helper function to receive all messages
  defp receive_all_messages(count \\ 0) do
    receive do
      {:perf_test, _} -> receive_all_messages(count + 1)
    after
      100 -> count
    end
  end
end
