# Complete GUROBIPY implementation

import gurobipy as gp
from gurobipy import GRB

def optimize_speaker_allocation():
    """Optimize the allocation of speakers to debates to maximize audience reach."""
    
    # 1. MODEL & DATA SETUP
    model = gp.Model("speaker_allocation")
    
    # Data from the database
    debates = [
        {"Debate_ID": 1, "Num_of_Audience": 150},
        {"Debate_ID": 2, "Num_of_Audience": 250},
        {"Debate_ID": 3, "Num_of_Audience": 100}
    ]
    
    speakers = [101, 102, 103]  # Example speaker IDs
    
    # Constraint bounds
    max_debates_per_speaker = 3
    max_speakers_per_debate = 5
    
    # Validate data lengths
    assert len(debates) > 0, "No debates available"
    assert len(speakers) > 0, "No speakers available"
    
    # 2. VARIABLES
    # Create binary decision variables x[i, j] for each speaker i and debate j
    x = model.addVars(
        [(i, j['Debate_ID']) for i in speakers for j in debates],
        vtype=GRB.BINARY,
        name="x"
    )
    
    # 3. OBJECTIVE FUNCTION
    # Maximize the total audience reached
    model.setObjective(
        gp.quicksum(j['Num_of_Audience'] * x[i, j['Debate_ID']] for i in speakers for j in debates),
        GRB.MAXIMIZE
    )
    
    # 4. CONSTRAINTS
    # Speaker Participation Limit
    model.addConstrs(
        (gp.quicksum(x[i, j['Debate_ID']] for j in debates) <= max_debates_per_speaker for i in speakers),
        name="speaker_participation_limit"
    )
    
    # Debate Capacity Limit
    model.addConstrs(
        (gp.quicksum(x[i, j['Debate_ID']] for i in speakers) <= max_speakers_per_debate for j in debates),
        name="debate_capacity_limit"
    )
    
    # 5. SOLVING & RESULTS
    model.optimize()
    
    if model.status == GRB.OPTIMAL:
        print(f"Optimal value: {model.objVal}")
        for i in speakers:
            for j in debates:
                if x[i, j['Debate_ID']].x > 1e-6:
                    print(f"Speaker {i} assigned to Debate {j['Debate_ID']}")
    elif model.status == GRB.INFEASIBLE:
        print("Problem is infeasible")
    elif model.status == GRB.UNBOUNDED:
        print("Problem is unbounded")
    
    return model

# Run the optimization
optimize_speaker_allocation()