To solve this optimization problem, we first need to define the decision variables, the objective function, and the constraints.

Let's denote:
- \(x_s\) as the number of soft cake-pops,
- \(x_m\) as the number of smooth cake-pops,
- \(x_c\) as the number of crunchy cake-pops.

The objective is to maximize profit. The profit per type of cake-pop is given, so the total profit \(P\) can be calculated as:
\[P = 4x_s + 6x_m + 5x_c\]

Now, let's consider the constraints based on the available ingredients:
1. **Batter constraint**: Each soft cake-pop requires 20 grams of batter, each smooth cake-pop requires 15 grams, and each crunchy cake-pop requires 12 grams. The total amount of batter available is 1000 grams.
   \[20x_s + 15x_m + 12x_c \leq 1000\]

2. **Milk constraint**: Each soft cake-pop requires 10 grams of milk, each smooth cake-pop requires 15 grams, and each crunchy cake-pop requires 18 grams. The total amount of milk available is 800 grams.
   \[10x_s + 15x_m + 18x_c \leq 800\]

3. **Non-negativity constraints**: Since the bakery cannot produce a negative number of cake-pops, we have:
   \[x_s \geq 0, x_m \geq 0, x_c \geq 0\]

Given these conditions, we aim to maximize \(P\) subject to the batter and milk constraints.

Now, let's translate this problem into Gurobi code in Python:

```python
from gurobipy import *

# Create a model
m = Model("cake_pops")

# Define decision variables
x_s = m.addVar(lb=0, vtype=GRB.INTEGER, name="soft_cake_pops")
x_m = m.addVar(lb=0, vtype=GRB.INTEGER, name="smooth_cake_pops")
x_c = m.addVar(lb=0, vtype=GRB.INTEGER, name="crunchy_cake_pops")

# Objective function: Maximize profit
m.setObjective(4*x_s + 6*x_m + 5*x_c, GRB.MAXIMIZE)

# Constraints
m.addConstr(20*x_s + 15*x_m + 12*x_c <= 1000, name="batter_constraint")
m.addConstr(10*x_s + 15*x_m + 18*x_c <= 800, name="milk_constraint")

# Solve the model
m.optimize()

# Print results
for v in m.getVars():
    print(f"{v.varName}: {v.x}")

print("Objective (Max Profit): ", m.objVal)
```