defmodule PlugIndie.Parser do
  import Plug.Conn

  def get_action(conn) do
    {action, body_params} = Map.pop(conn.body_params, "action")
    conn = %Plug.Conn{conn | body_params: body_params}

    case action do
      nil ->
        {:ok, :create, conn}

      action when action in ["delete", "undelete", "update"] ->
        {:ok, String.to_existing_atom(action), conn}

      _ ->
        {:error, :invalid_request, "Invalid action supplied."}
    end
  end

  def get_query(conn) do
    case Map.fetch(conn.query_params, "q") do
      {:ok, query} when query in ["config", "source", "syndicate-to"] ->
        {:ok, String.to_existing_atom(query)}

      _ ->
        {:error, :invalid_request, "Invalid query supplied."}
    end
  end

  def get_file(conn) do
    case Map.fetch(conn.body_params, "file") do
      {:ok, file} -> {:ok, file}
      :error -> {:error, :invalid_request, "Invalid file supplied."}
    end
  end

  def get_access_token(conn) do
    {access_token, body_params} = Map.pop(conn.body_params, "access_token")
    conn = %Plug.Conn{conn | body_params: body_params}

    case access_token do
      nil -> parse_auth_header(conn)
      access_token -> {:ok, access_token, conn}
    end
  end

  defp parse_auth_header(conn) do
    with [header] <- get_req_header(conn, "authorization"),
         "Bearer" <> token <- header,
         do: {:ok, String.trim(token), conn},
         else: (_ -> {:error, :unauthorized, "Authentication from header failed."})
  end

  def parse_create_body("application/json", params) do
    with {:ok, ["h-" <> type]} <- Map.fetch(params, "type"),
         {:ok, properties} when is_map(properties) <- Map.fetch(params, "properties") do
      {:ok, type, Map.new(properties)}
    else
      _ ->
        {:error, :invalid_request}
    end
  end

  def parse_create_body(_, params) do
    with {type, params} when is_binary(type) <- Map.pop(params, "h") do
      properties =
        params
        |> Enum.map(fn {k, v} -> {k, List.wrap(v)} end)
        |> Map.new()

      {:ok, type, properties}
    else
      _ -> {:error, :invalid_request}
    end
  end

  def parse_update_properties(properties) do
    properties = Map.take(properties, ["replace", "add", "delete"])

    valid? =
      Enum.all?(properties, fn
        {"delete", prop} when is_list(prop) ->
          Enum.all?(prop, &is_binary/1)

        {_k, prop} when is_map(prop) ->
          Enum.all?(prop, fn
            {_k, v} when is_list(v) -> true
            _ -> false
          end)

        _ ->
          false
      end)

    if valid? do
      replace = Map.get(properties, "replace", %{})
      add = Map.get(properties, "add", %{})
      delete = Map.get(properties, "delete", %{})
      {:ok, replace, add, delete}
    else
      :error
    end
  end
end