defmodule TestServer.WebSocket do
  @moduledoc false

  alias TestServer.Instance

  def handle_frame(frame, {{instance, _route_ref} = socket, state}) do
    case Instance.dispatch(socket, {:websocket, {:handle, frame}, state}) do
      {:ok, result} ->
        handle_reply(result, socket)

      {:error, :not_found} ->
        message =
          "#{Instance.format_instance(instance)} received an unexpected WebSocket frame"
          |> append_formatted_frame(frame)
          |> append_formatted_websocket_handlers(socket)

        reply_with_error({socket, state}, {RuntimeError.exception(message), []})

      {:error, {error, stacktrace}} ->
        reply_with_error({socket, state}, {error, stacktrace})
    end
  end

  defp reply_with_error({{instance, _route_ref}, state}, {exception, stacktrace}) do
    Instance.report_error(instance, {exception, stacktrace})

    {:reply, :ok, {:text, Exception.format(:error, exception, stacktrace)}, state}
  end

  defp append_formatted_frame(message, frame) do
    """
    #{message}:

    #{inspect(frame)}
    """
  end

  defp append_formatted_websocket_handlers(message, {instance, route_ref}) do
    websocket_handlers =
      Instance.websocket_handlers(instance)
      |> Enum.filter(&(&1.route_ref == route_ref))
      |> Enum.split_with(&(not &1.suspended))

    """
    #{message}

    #{format_websocket_handlers(websocket_handlers)}
    """
  end

  defp format_websocket_handlers({[], suspended_websocket_handlers}) do
    message = "No active websocket handlers."

    case suspended_websocket_handlers do
      [] ->
        message

      websocket_handlers ->
        """
        #{message} The following websocket handlers have been processed:

        #{Instance.format_routes(websocket_handlers)}"
        """
    end
  end

  defp format_websocket_handlers({active_websocket_handlers, _}) do
    """
    Active websocket handlers:

    #{Instance.format_websocket_handlers(active_websocket_handlers)}
    """
  end

  defp handle_reply({:reply, frame, state}, socket), do: {:reply, :ok, frame, {socket, state}}
  defp handle_reply({:ok, state}, socket), do: {:ok, {socket, state}}

  def handle_info({callback, stacktrace}, {socket, state}) do
    case Instance.dispatch(socket, {:websocket, {:info, callback, stacktrace}, state}) do
      {:ok, result} -> handle_reply(result, socket)
      {:error, {error, stacktrace}} -> reply_with_error({socket, state}, {error, stacktrace})
    end
  end
end
