#!/usr/bin/env python3
"""
Pyomo 6.9.2 Implementation for Product Pricing Optimization
"""

import pyomo.environ as pyo
from pyomo.opt import SolverFactory

def product_pricing_optimization():
    """Optimize product pricing across currencies to maximize revenue."""
    
    # 1. MODEL CREATION
    model = pyo.ConcreteModel()
    
    # 2. DATA SETUP
    # Exchange rates
    usd_to_eur = 0.92
    usd_to_gbp = 0.79
    eur_to_gbp = 0.86
    max_price_diff = 5.0
    
    # Product data
    products = [1, 2, 3]
    sales_volume = {1: 150, 2: 200, 3: 100}
    
    # Price constraints
    min_price_dollars = {1: 10.0, 2: 15.0, 3: 20.0}
    max_price_dollars = {1: 50.0, 2: 55.0, 3: 60.0}
    min_price_euros = {1: 9.0, 2: 14.0, 3: 19.0}
    max_price_euros = {1: 45.0, 2: 50.0, 3: 55.0}
    min_price_pounds = {1: 8.0, 2: 13.0, 3: 18.0}
    max_price_pounds = {1: 40.0, 2: 45.0, 3: 50.0}
    
    # Validate array lengths
    assert len(products) == len(sales_volume) == len(min_price_dollars) == len(max_price_dollars) == len(min_price_euros) == len(max_price_euros) == len(min_price_pounds) == len(max_price_pounds), "Array length mismatch"
    
    # 3. SETS
    model.I = pyo.Set(initialize=products)  # Set of products
    
    # 4. PARAMETERS
    model.sales_volume = pyo.Param(model.I, initialize=sales_volume)
    model.min_price_dollars = pyo.Param(model.I, initialize=min_price_dollars)
    model.max_price_dollars = pyo.Param(model.I, initialize=max_price_dollars)
    model.min_price_euros = pyo.Param(model.I, initialize=min_price_euros)
    model.max_price_euros = pyo.Param(model.I, initialize=max_price_euros)
    model.min_price_pounds = pyo.Param(model.I, initialize=min_price_pounds)
    model.max_price_pounds = pyo.Param(model.I, initialize=max_price_pounds)
    
    # 5. VARIABLES
    model.price_dollars = pyo.Var(model.I, within=pyo.NonNegativeReals)
    model.price_euros = pyo.Var(model.I, within=pyo.NonNegativeReals)
    model.price_pounds = pyo.Var(model.I, within=pyo.NonNegativeReals)
    
    # 6. OBJECTIVE FUNCTION
    def obj_rule(model):
        return sum(model.sales_volume[i] * (model.price_dollars[i] + model.price_euros[i] + model.price_pounds[i]) for i in model.I)
    model.objective = pyo.Objective(rule=obj_rule, sense=pyo.maximize)
    
    # 7. CONSTRAINTS
    
    # Price bounds
    def price_bounds_dollars_rule(model, i):
        return (model.min_price_dollars[i], model.price_dollars[i], model.max_price_dollars[i])
    model.price_bounds_dollars = pyo.Constraint(model.I, rule=price_bounds_dollars_rule)
    
    def price_bounds_euros_rule(model, i):
        return (model.min_price_euros[i], model.price_euros[i], model.max_price_euros[i])
    model.price_bounds_euros = pyo.Constraint(model.I, rule=price_bounds_euros_rule)
    
    def price_bounds_pounds_rule(model, i):
        return (model.min_price_pounds[i], model.price_pounds[i], model.max_price_pounds[i])
    model.price_bounds_pounds = pyo.Constraint(model.I, rule=price_bounds_pounds_rule)
    
    # Currency price differences
    def currency_diff_usd_eur_rule(model, i):
        return abs(usd_to_eur * model.price_dollars[i] - model.price_euros[i]) <= max_price_diff
    model.currency_diff_usd_eur = pyo.Constraint(model.I, rule=currency_diff_usd_eur_rule)
    
    def currency_diff_usd_gbp_rule(model, i):
        return abs(usd_to_gbp * model.price_dollars[i] - model.price_pounds[i]) <= max_price_diff
    model.currency_diff_usd_gbp = pyo.Constraint(model.I, rule=currency_diff_usd_gbp_rule)
    
    def currency_diff_eur_gbp_rule(model, i):
        return abs(eur_to_gbp * model.price_euros[i] - model.price_pounds[i]) <= max_price_diff
    model.currency_diff_eur_gbp = pyo.Constraint(model.I, rule=currency_diff_eur_gbp_rule)
    
    # 8. SOLVING WITH GUROBI
    solver = SolverFactory('gurobi')
    
    # Optional: Set solver options
    solver.options['TimeLimit'] = 300  # 5 minutes
    solver.options['MIPGap'] = 0.01    # 1% gap
    
    # Solve the model
    results = solver.solve(model, tee=True)  # tee=True shows solver output
    
    # 9. RESULT PROCESSING
    # Check solver status
    if results.solver.termination_condition == pyo.TerminationCondition.optimal:
        print("Optimal solution found!")
        print(f"Optimal value: {pyo.value(model.objective)}")
        
        # Extract variable values
        print("\nVariable values:")
        for i in model.I:
            print(f"Product {i}:")
            print(f"  Price in USD: {pyo.value(model.price_dollars[i]):.2f}")
            print(f"  Price in EUR: {pyo.value(model.price_euros[i]):.2f}")
            print(f"  Price in GBP: {pyo.value(model.price_pounds[i]):.2f}")
        
    elif results.solver.termination_condition == pyo.TerminationCondition.infeasible:
        print("Problem is infeasible")
    elif results.solver.termination_condition == pyo.TerminationCondition.unbounded:
        print("Problem is unbounded")
    else:
        print(f"Solver terminated with condition: {results.solver.termination_condition}")
    
    return model

if __name__ == "__main__":
    product_pricing_optimization()