# Complete GUROBIPY implementation - Retry Attempt 4

import gurobipy as gp
from gurobipy import GRB

def optimize_casting():
    # 1. MODEL & DATA SETUP
    model = gp.Model("musical_casting")

    # Data from the database schema
    musical_requirements = {
        1: 3,
        2: 4,
        3: 5
    }
    
    actor_details = {
        101: 25,
        102: 30,
        103: 35,
        104: 28,
        105: 32
    }
    
    # Extracting lists of actors and musicals
    actors = list(actor_details.keys())
    musicals = list(musical_requirements.keys())
    
    # Validate lengths (not strictly necessary here, but good practice)
    assert len(actors) > 0, "No actors available"
    assert len(musicals) > 0, "No musicals available"
    
    # 2. VARIABLES
    # Binary decision variables for actor assignments
    x = model.addVars(actors, musicals, vtype=GRB.BINARY, name="x")
    
    # 3. OBJECTIVE FUNCTION
    # Minimize the total age of the actors assigned to the musicals
    model.setObjective(gp.quicksum(actor_details[i] * x[i, j] for i in actors for j in musicals), GRB.MINIMIZE)
    
    # 4. CONSTRAINTS
    
    # Each musical must have exactly the number of actors it requires
    for j in musicals:
        model.addConstr(gp.quicksum(x[i, j] for i in actors) == musical_requirements[j], name=f"musical_req_{j}")
    
    # Each actor can only be assigned to one musical
    for i in actors:
        model.addConstr(gp.quicksum(x[i, j] for j in musicals) <= 1, name=f"actor_assign_{i}")
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in actors:
            for j in musicals:
                if x[i, j].x > 0.5:  # Binary variable, so check if it's 1
                    print(f"Actor {i} is assigned to musical {j}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    else:
        print("Optimization ended with status", model.status)

    return model

# Run the optimization
optimize_casting()