input: Write a function that generates a fuzzy search algorithm in Python which can match strings with near similarities using the Levenshtein distance. The function should take a query string, a list of choices, and a threshold for the Levenshtein distance. It should return a list of matches that are within the specified threshold.
  
  def find_matches(query, choices, threshold):
      # Convert query and choices to lower case
      query, choices = query.lower(), [choice.lower() for choice in choices]
      # Initialize empty list to store matches
      matches = []
      
      # Loop through each choice
      for choice in choices:
          # Calculate Levenshtein distance between query and choice
          distance = levenshtein_distance(query, choice)
          # If distance is less than threshold, append choice to matches
          if distance <= threshold:
              matches.append(choice)
      
      return matches
  
  assert find_matches("apple", ["apples", "pineapple", "appel", "appl"], 2)==["apples", "appel", "appl"]
  assert find_matches("banana", ["ananab", "banano", "bandana", "bananana"], 3)==["ananab", "banano", "bananana"]
  assert find_matches("grape", ["grapes", "grapejuice", "grapevine", "grape"], 1)==["grapes", "grape"]
  assert find_matches("orange", ["oranges", "orangej", "oorange", "orangee"], 2)==["oranges", "orangej", "orangee"]
  assert find_matches("pine", ["pineapple", "pinecone", "pinenut", "piney"], 4)==["pineapple", "pinecone", "pinenut", "piney"]
output: def find_matches(query, choices, threshold):
      query = query.lower()
      choices = [choice.lower() for choice in choices]
      matches = []
      
      def levenshtein_distance(s1, s2):
          if len(s1) < len(s2):
              return levenshtein_distance(s2, s1)
          if len(s2) == 0:
              return len(s1)
          previous_row = range(len(s2) + 1)
          for i, c1 in enumerate(s1):
              current_row = [i + 1]
              for j, c2 in enumerate(s2):
                  insertions = previous_row[j + 1] + 1
                  deletions = current_row[j] + 1
                  substitutions = previous_row[j] + (c1 != c2)
                  current_row.append(min(insertions, deletions, substitutions))
              previous_row = current_row
          return previous_row[-1]
      
      for choice in choices:
          distance = levenshtein_distance(query, choice)
          if distance <= threshold:
              matches.append(choice)
      
      return matches