defmodule Aprsme.ConnectionMonitorTest do
  use ExUnit.Case, async: false

  alias Aprsme.ConnectionMonitor

  describe "cluster disabled (default test config)" do
    test "init/1 returns :ignore when cluster disabled" do
      assert ConnectionMonitor.init([]) == :ignore
    end

    test "accepting_connections?/0 returns true when cluster disabled" do
      assert ConnectionMonitor.accepting_connections?() == true
    end

    test "get_stats/0 returns default stats when cluster disabled" do
      assert ConnectionMonitor.get_stats() == %{connections: 0, cpu: 0.0, memory: 0.0}
    end
  end

  describe "cluster enabled without a running monitor" do
    test "get_stats/0 returns default stats instead of crashing" do
      Application.put_env(:aprsme, :cluster_enabled, true)

      on_exit(fn ->
        Application.put_env(:aprsme, :cluster_enabled, false)
      end)

      assert ConnectionMonitor.get_stats() == %{connections: 0, cpu: 0.0, memory: 0.0}
    end
  end

  describe "cluster enabled" do
    setup do
      Application.put_env(:aprsme, :cluster_enabled, true)
      {:ok, pid} = ConnectionMonitor.start_link([])

      on_exit(fn ->
        if Process.alive?(pid) do
          try do
            GenServer.stop(pid)
          catch
            :exit, _ -> :ok
          end
        end

        Application.put_env(:aprsme, :cluster_enabled, false)
      end)

      %{pid: pid}
    end

    test "register_connection/0 increments connections" do
      ConnectionMonitor.register_connection()
      # Allow the cast to be processed
      stats = ConnectionMonitor.get_stats()
      assert stats.connections == 1
    end

    test "unregister_connection/0 decrements connections" do
      ConnectionMonitor.register_connection()
      ConnectionMonitor.register_connection()
      ConnectionMonitor.unregister_connection()
      stats = ConnectionMonitor.get_stats()
      assert stats.connections == 1
    end

    test "unregister_connection/0 does not go below 0" do
      ConnectionMonitor.unregister_connection()
      stats = ConnectionMonitor.get_stats()
      assert stats.connections == 0
    end

    test "accepting_connections?/0 returns true when not draining" do
      assert ConnectionMonitor.accepting_connections?() == true
    end

    test "accepting_connections?/0 returns false when draining", %{pid: pid} do
      :sys.replace_state(pid, fn state -> %{state | draining: true} end)
      assert ConnectionMonitor.accepting_connections?() == false
    end

    test ":check_load handler keeps process alive", %{pid: pid} do
      send(pid, :check_load)
      # Give it time to process
      _ = ConnectionMonitor.get_stats()
      assert Process.alive?(pid)
    end

    test "get_stats/0 returns cpu and memory fields", %{pid: _pid} do
      stats = ConnectionMonitor.get_stats()

      assert Map.has_key?(stats, :cpu)
      assert Map.has_key?(stats, :memory)
      assert is_number(stats.cpu)
      assert is_number(stats.memory)
    end

    test "draining triggers when connections exceed imbalance ratio", %{pid: pid} do
      # Simulate having many connections to trigger draining analysis
      for _ <- 1..20 do
        ConnectionMonitor.register_connection()
      end

      # Force a load check which will gather stats and analyze
      send(pid, :check_load)
      Process.sleep(100)

      assert Process.alive?(pid)
      stats = ConnectionMonitor.get_stats()
      assert stats.connections == 20
    end

    test "register_connection/0 is a no-op when cluster disabled" do
      Application.put_env(:aprsme, :cluster_enabled, false)

      # Should not crash even though the GenServer is running
      assert ConnectionMonitor.register_connection() == nil

      Application.put_env(:aprsme, :cluster_enabled, true)
    end

    test "unregister_connection/0 is a no-op when cluster disabled" do
      Application.put_env(:aprsme, :cluster_enabled, false)

      assert ConnectionMonitor.unregister_connection() == nil

      Application.put_env(:aprsme, :cluster_enabled, true)
    end

    test "draining is set to true when state has high connections", %{pid: pid} do
      # Set up state with draining true and connections > 0 to trigger drain broadcast
      Phoenix.PubSub.subscribe(Aprsme.PubSub, "connection:drain:#{Node.self()}")

      :sys.replace_state(pid, fn state ->
        %{state | draining: true, local_connections: 50}
      end)

      # Trigger check_load to run the full analysis pipeline
      send(pid, :check_load)
      Process.sleep(200)

      assert Process.alive?(pid)
      assert %{connections: 50} = ConnectionMonitor.get_stats()
    end

    test "get_stats returns draining field", %{pid: pid} do
      :sys.replace_state(pid, fn state -> %{state | draining: true} end)
      stats = ConnectionMonitor.get_stats()
      assert stats.draining == true
    end

    test "gather_cluster_stats includes local node stats without deadlock", %{pid: pid} do
      ConnectionMonitor.register_connection()
      ConnectionMonitor.register_connection()

      # Trigger check_load which calls gather_cluster_stats internally.
      # Before the fix, this would deadlock for 5s because gather_cluster_stats
      # did an RPC call to Node.self() which GenServer.call'd back into itself.
      send(pid, :check_load)
      Process.sleep(100)

      state = :sys.get_state(pid)
      # Local node stats must always be present
      assert Map.has_key?(state.node_stats, Node.self())
      assert state.node_stats[Node.self()].connections == 2
    end
  end
end
