Here's the formulation of the linear program and the Gurobi code to solve it:

**Decision Variables:**

* `x`: Number of soft cake-pops
* `y`: Number of smooth cake-pops
* `z`: Number of crunchy cake-pops

**Objective Function:**

Maximize profit:  4*x + 6*y + 5*z

**Constraints:**

* Batter constraint: 20*x + 15*y + 12*z <= 1000
* Milk constraint: 10*x + 15*y + 18*z <= 800
* Non-negativity constraints: x, y, z >= 0


```python
import gurobipy as gp
from gurobipy import GRB

# Create a new model
model = gp.Model("cake_pop_optimization")

# Create variables
x = model.addVar(vtype=GRB.INTEGER, name="soft")
y = model.addVar(vtype=GRB.INTEGER, name="smooth")
z = model.addVar(vtype=GRB.INTEGER, name="crunchy")

# Set objective function
model.setObjective(4*x + 6*y + 5*z, GRB.MAXIMIZE)

# Add constraints
model.addConstr(20*x + 15*y + 12*z <= 1000, "batter_constraint")
model.addConstr(10*x + 15*y + 18*z <= 800, "milk_constraint")

# Optimize model
model.optimize()

# Print results
if model.status == GRB.OPTIMAL:
    print(f"Optimal profit: {model.objVal}")
    print(f"Number of soft cake-pops: {x.x}")
    print(f"Number of smooth cake-pops: {y.x}")
    print(f"Number of crunchy cake-pops: {z.x}")
elif model.status == GRB.INFEASIBLE:
    print("The model is infeasible.")
else:
    print(f"Optimization ended with status {model.status}")

```
