## Symbolic Representation of the Optimization Problem

The optimization problem can be symbolically represented as follows:

```json
{
    'sym_variables': [
        ('x1', 'almond bubble tea bottles'),
        ('x2', 'ginger bubble tea bottles')
    ],
    'objective_function': '5*x1 + 9*x2',
    'constraints': [
        'x1 <= 120',
        'x2 <= 200',
        'x1 + x2 <= 300',
        'x1 >= 0',
        'x2 >= 0'
    ]
}
```

## Gurobi Code

Here is the Gurobi code in Python that solves the optimization problem:

```python
import gurobi

def solve_bubble_tea_problem():
    # Create a new Gurobi model
    model = gurobi.Model()

    # Define the variables
    x1 = model.addVar(lb=0, name="almond_bubble_tea")
    x2 = model.addVar(lb=0, name="ginger_bubble_tea")

    # Define the objective function
    model.setObjective(5 * x1 + 9 * x2, gurobi.GRB.MAXIMIZE)

    # Define the constraints
    model.addConstr(x1 <= 120, name="almond_demand_constraint")
    model.addConstr(x2 <= 200, name="ginger_demand_constraint")
    model.addConstr(x1 + x2 <= 300, name="total_production_constraint")

    # Optimize the model
    model.optimize()

    # Print the solution
    if model.status == gurobi.GRB.OPTIMAL:
        print("Optimal solution found.")
        print(f"Almond bubble tea bottles: {x1.varValue}")
        print(f"Ginger bubble tea bottles: {x2.varValue}")
        print(f"Maximum profit: {model.objVal}")
    else:
        print("No optimal solution found.")

solve_bubble_tea_problem()
```

This Gurobi code defines the variables, objective function, and constraints based on the symbolic representation of the optimization problem. It then solves the model and prints the optimal solution, including the number of almond and ginger bubble tea bottles to produce and the maximum profit.