#!/usr/bin/env python3
"""
Gurobipy Implementation for Musical Actor-Role Assignment Optimization
"""

import gurobipy as gp
from gurobipy import GRB

def optimize_actor_role_assignment():
    """Optimize actor-role assignments to maximize audience engagement."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("actor_role_assignment")
    
    # Data from the problem context
    actors = [
        {'actor_id': 1, 'age': 28, 'performance_duration': 15},
        {'actor_id': 2, 'age': 35, 'performance_duration': 20},
        {'actor_id': 3, 'age': 22, 'performance_duration': 10}
    ]
    
    roles = [
        {'role_id': 1, 'musical_id': 1, 'role_name': 'Lead'},
        {'role_id': 2, 'musical_id': 1, 'role_name': 'Supporting'},
        {'role_id': 3, 'musical_id': 2, 'role_name': 'Chorus'}
    ]
    
    # Weights for engagement metric
    age_weight = 0.6
    duration_weight = 0.4
    
    # Maximum total duration limit
    max_total_duration = 100
    
    # CRITICAL: Validate array lengths before loops
    assert len(actors) > 0, "No actors provided"
    assert len(roles) > 0, "No roles provided"
    
    # 2. VARIABLES
    # Binary decision variables: x[a][r] = 1 if actor a is assigned to role r
    x = model.addVars(
        [(a['actor_id'], r['role_id']) for a in actors for r in roles],
        vtype=GRB.BINARY,
        name="x"
    )
    
    # 3. OBJECTIVE FUNCTION
    # Maximize total engagement: sum over all actor-role pairs of (0.6 * age + 0.4 * duration) * x[a][r]
    model.setObjective(
        gp.quicksum(
            (age_weight * a['age'] + duration_weight * a['performance_duration']) * x[a['actor_id'], r['role_id']]
            for a in actors for r in roles
        ),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    
    # Constraint 1: Each actor can be assigned to at most one role
    for a in actors:
        model.addConstr(
            gp.quicksum(x[a['actor_id'], r['role_id']] for r in roles) <= 1,
            name=f"actor_limit_{a['actor_id']}"
        )
    
    # Constraint 2: Each role must be filled by exactly one actor
    for r in roles:
        model.addConstr(
            gp.quicksum(x[a['actor_id'], r['role_id']] for a in actors) == 1,
            name=f"role_fulfillment_{r['role_id']}"
        )
    
    # Constraint 3: Total duration of all performances must not exceed 100 minutes
    model.addConstr(
        gp.quicksum(
            a['performance_duration'] * x[a['actor_id'], r['role_id']]
            for a in actors for r in roles
        ) <= max_total_duration,
        name="total_duration_limit"
    )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for a in actors:
            for r in roles:
                if x[a['actor_id'], r['role_id']].x > 0.5:
                    print(f"Actor {a['actor_id']} assigned to Role {r['role_id']}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Execute the optimization
if __name__ == "__main__":
    optimize_actor_role_assignment()