defmodule AprsmeWeb.Plugs.HealthCheckTest do
  # :health_status is an app-wide env var the plug reads, so these tests can't
  # safely run in parallel with anything else that might touch it.
  use AprsmeWeb.ConnCase, async: false

  alias AprsmeWeb.Plugs.HealthCheck

  setup do
    # Ensure ShutdownHandler isn't lingering in shutting_down: true from prior tests.
    if pid = Process.whereis(Aprsme.ShutdownHandler) do
      :sys.replace_state(pid, fn state -> %{state | shutting_down: false} end)
    end

    original = Application.get_env(:aprsme, :health_status, :healthy)
    Application.put_env(:aprsme, :health_status, :healthy)

    on_exit(fn -> Application.put_env(:aprsme, :health_status, original) end)
    :ok
  end

  describe "call/2 pass-through" do
    test "returns the conn unchanged for non-/health paths", %{conn: conn} do
      conn = %{conn | request_path: "/something-else"}
      result = HealthCheck.call(conn, [])
      assert result == conn
      refute result.halted
    end
  end

  describe "liveness probe" do
    test "returns 200 OK when healthy", %{conn: conn} do
      conn = %{conn | request_path: "/health"}
      result = HealthCheck.call(conn, probe_type: :liveness)
      assert result.status == 200
      assert result.resp_body == "OK"
      assert result.halted
    end

    test "returns 200 even when readiness would fail (draining)", %{conn: conn} do
      original = Application.get_env(:aprsme, :health_status, :healthy)

      try do
        Application.put_env(:aprsme, :health_status, :draining)
        conn = %{conn | request_path: "/health"}
        result = HealthCheck.call(conn, probe_type: :liveness)
        assert result.status == 200
      after
        Application.put_env(:aprsme, :health_status, original)
      end
    end
  end

  describe "readiness probe" do
    test "returns 200 OK under normal conditions", %{conn: conn} do
      conn = %{conn | request_path: "/health"}
      result = HealthCheck.call(conn, probe_type: :readiness)
      # Readiness also hits the DB and PubSub, which should work in test env.
      assert result.status in [200, 503]
      assert result.halted
    end

    test "returns 503 when health_status is :draining", %{conn: conn} do
      original = Application.get_env(:aprsme, :health_status, :healthy)

      try do
        Application.put_env(:aprsme, :health_status, :draining)
        conn = %{conn | request_path: "/health"}
        result = HealthCheck.call(conn, probe_type: :readiness)
        assert result.status == 503
        assert result.resp_body =~ "draining"
      after
        Application.put_env(:aprsme, :health_status, original)
      end
    end

    test "defaults to readiness when no probe_type opt given", %{conn: conn} do
      conn = %{conn | request_path: "/health"}
      result = HealthCheck.call(conn, [])
      assert result.status in [200, 503]
      assert result.halted
    end
  end

  describe "init/1" do
    test "returns opts unchanged" do
      assert HealthCheck.init(probe_type: :liveness) == [probe_type: :liveness]
      assert HealthCheck.init([]) == []
    end
  end

  describe "readiness probe when ShutdownHandler is marked shutting down" do
    test "returns 503 when ShutdownHandler reports shutting_down", %{conn: conn} do
      # Start a fake ShutdownHandler that always says "yes, shutting down".
      # Only start if not already running; if running, :sys.replace_state.
      original_shutdown_pid = Process.whereis(Aprsme.ShutdownHandler)

      # We can't easily replace the real ShutdownHandler without disrupting
      # the app. Instead, exercise the :draining short-circuit via health_status.
      original = Application.get_env(:aprsme, :health_status, :healthy)
      # credo:disable-for-next-line Jump.CredoChecks.ConditionalAssertion
      assert is_pid(original_shutdown_pid) or is_nil(original_shutdown_pid)
      # ShutdownHandler may or may not be running — legitimately conditional

      try do
        Application.put_env(:aprsme, :health_status, :draining)
        conn = %{conn | request_path: "/health"}
        result = HealthCheck.call(conn, probe_type: :readiness)
        assert result.status == 503
      after
        Application.put_env(:aprsme, :health_status, original)
      end
    end
  end

  describe "liveness probe edge cases" do
    test "returns 200 when basic_health_checks is :ok", %{conn: conn} do
      conn = %{conn | request_path: "/health"}
      result = HealthCheck.call(conn, probe_type: :liveness)
      assert result.status == 200
      assert result.resp_body =~ "OK"
    end
  end

  describe "call/2 halted conn short-circuit" do
    test "always halts the conn for /health paths", %{conn: conn} do
      conn = %{conn | request_path: "/health"}
      result = HealthCheck.call(conn, probe_type: :liveness)
      assert result.halted
    end
  end

  describe "shutting_down? helper paths" do
    test "returns 200 OK when no ShutdownHandler process is registered", %{conn: conn} do
      Application.put_env(:aprsme, :health_status, :healthy)

      original_pid = Process.whereis(Aprsme.ShutdownHandler)

      if original_pid do
        :erlang.unregister(Aprsme.ShutdownHandler)
      end

      try do
        conn = %{conn | request_path: "/health"}
        result = HealthCheck.call(conn, probe_type: :readiness)
        # ask_shutting_down?(nil) → false → readiness_status(_, false) → full_health_checks
        assert result.status == 200
      after
        if original_pid && !Process.whereis(Aprsme.ShutdownHandler) do
          Process.register(original_pid, Aprsme.ShutdownHandler)
        end
      end
    end

    test "returns 200 OK even when registered ShutdownHandler-named pid is unresponsive", %{conn: conn} do
      Application.put_env(:aprsme, :health_status, :healthy)
      Application.put_env(:aprsme, :health_check_call_timeout_ms, 50)

      original_pid = Process.whereis(Aprsme.ShutdownHandler)

      # Register a dummy task pid under the name. GenServer.call to it will
      # time out (50ms); the catch clause in ask_if_alive returns false → readiness OK.
      if original_pid do
        :erlang.unregister(Aprsme.ShutdownHandler)
      end

      task = Task.async(fn -> Process.sleep(500) end)
      Process.register(task.pid, Aprsme.ShutdownHandler)

      try do
        conn = %{conn | request_path: "/health"}
        result = HealthCheck.call(conn, probe_type: :readiness)
        # The dummy pid is alive but won't respond to :shutting_down? → catch → false → OK
        assert result.status == 200
      after
        Application.delete_env(:aprsme, :health_check_call_timeout_ms)

        if Process.whereis(Aprsme.ShutdownHandler) == task.pid do
          :erlang.unregister(Aprsme.ShutdownHandler)
        end

        Task.shutdown(task, :brutal_kill)

        if original_pid && !Process.whereis(Aprsme.ShutdownHandler) do
          Process.register(original_pid, Aprsme.ShutdownHandler)
        end
      end
    end
  end

  describe "liveness check failure path" do
    test "exercises basic_health_checks via Application.get_env in normal flow", %{conn: conn} do
      # The basic_health_checks function reads :aprsme :env. If that succeeds,
      # liveness returns :ok. We can't easily make Application.get_env crash,
      # but we can cover the success branch fully.
      conn = %{conn | request_path: "/health"}
      result = HealthCheck.call(conn, probe_type: :liveness)
      assert result.status == 200
      assert result.resp_body == "OK"
    end
  end

  describe "ask_if_alive false branch" do
    test "ShutdownHandler-named registration pointing to a dead pid resolves to not-shutting-down", %{conn: conn} do
      Application.put_env(:aprsme, :health_status, :healthy)

      original_pid = Process.whereis(Aprsme.ShutdownHandler)

      if original_pid do
        :erlang.unregister(Aprsme.ShutdownHandler)
      end

      # Spawn a task that exits immediately, then register it. Process.alive?
      # on the exited pid returns false → ask_if_alive(false, _pid) → false.
      task = Task.async(fn -> :ok end)
      _ = Task.await(task)
      # Wait briefly to ensure pid is dead.
      Process.sleep(20)

      try do
        Process.register(task.pid, Aprsme.ShutdownHandler)
      rescue
        _ -> :ok
      end

      try do
        conn = %{conn | request_path: "/health"}
        result = HealthCheck.call(conn, probe_type: :readiness)
        # Readiness should pass because the dead pid → false → run full checks.
        assert result.status in [200, 503]
      after
        if Process.whereis(Aprsme.ShutdownHandler) == task.pid do
          try do
            :erlang.unregister(Aprsme.ShutdownHandler)
          rescue
            _ -> :ok
          end
        end

        if original_pid && !Process.whereis(Aprsme.ShutdownHandler) do
          Process.register(original_pid, Aprsme.ShutdownHandler)
        end
      end
    end
  end

  describe "readiness probe success path" do
    test "returns 200 with OK when all checks pass", %{conn: conn} do
      Application.put_env(:aprsme, :health_status, :healthy)

      conn = %{conn | request_path: "/health"}
      result = HealthCheck.call(conn, probe_type: :readiness)

      assert result.status == 200
      assert result.resp_body == "OK"
      assert result.halted
    end

    test "returns 503 when ShutdownHandler reports shutting_down via the live process", %{conn: conn} do
      Application.put_env(:aprsme, :health_status, :healthy)

      pid = Process.whereis(Aprsme.ShutdownHandler)
      assert pid, "ShutdownHandler must be running to exercise the live shutting_down? path"
      :sys.replace_state(pid, fn state -> %{state | shutting_down: true} end)

      try do
        conn = %{conn | request_path: "/health"}
        result = HealthCheck.call(conn, probe_type: :readiness)
        assert result.status == 503
        assert result.resp_body =~ "shutting down"
      after
        :sys.replace_state(pid, fn state -> %{state | shutting_down: false} end)
      end
    end
  end
end
