#!/usr/bin/env python3

# Bot_Allokation.py
"""
Kapitel Einfuehrung: Erstes Optimierungsmodell mit Google OR-Tools (CP-SAT).
Problem: Server-Allokation für Trading-Bots.

Modell mit expliziter Nebenbedingung statt versteckter Variablengrenze,
vollständiger Statusauswertung und Ergebnisprüfung.
"""

from ortools.sat.python import cp_model

# --- Parameter (die "Fakten" des Problems) ---------------------------------
GEWINN_A, GEWINN_B = 150, 250      # Euro pro Bot und Tag
CPU_A, CPU_B = 2, 5                # vCPU-Bedarf je Bot
RAM_A, RAM_B = 4, 6                # GB Arbeitsspeicher je Bot
CPU_GESAMT, RAM_GESAMT = 40, 60    # verfügbare Kapazitäten
MAX_ARBITRAGE = 8                  # Marktliquiditätsgrenze


def loese_bot_allokation(zeitlimit_s: float = 10.0):
    """Baut das Modell, löst es und gibt eine Auswertung aus."""

    # 1. Modell instanziieren
    modell = cp_model.CpModel()

    # 2. Entscheidungsvariablen anlegen
    #    NewIntVar(untere_grenze, obere_grenze, name)
    #    Die obere Grenze ist bewusst großzügig; die echte Beschränkung
    #    formulieren wir unten als Nebenbedingung, damit das Modell die
    #    mathematische Formulierung 1:1 abbildet.
    x_a = modell.NewIntVar(0, 100, "Arbitrage_Bots")
    x_b = modell.NewIntVar(0, 100, "Trend_Bots")

    # 3. Nebenbedingungen definieren
    c_cpu = modell.Add(CPU_A * x_a + CPU_B * x_b <= CPU_GESAMT)      # vCPU-Limit
    c_ram = modell.Add(RAM_A * x_a + RAM_B * x_b <= RAM_GESAMT)      # RAM-Limit
    c_liq = modell.Add(x_a <= MAX_ARBITRAGE)                          # Marktliquidität

    # 4. Zielfunktion: Maximiere den Tagesgewinn
    modell.Maximize(GEWINN_A * x_a + GEWINN_B * x_b)

    # 5. Solver konfigurieren und ausführen
    loeser = cp_model.CpSolver()
    loeser.parameters.max_time_in_seconds = zeitlimit_s
    status = loeser.Solve(modell)

    # 6. Status auswerten -- IMMER alle Fälle behandeln
    status_text = loeser.StatusName(status)
    if status not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
        print(f"Keine verwertbare Lösung. Solver-Status: {status_text}")
        if status == cp_model.INFEASIBLE:
            print("Das Modell ist unlösbar - die Bedingungen widersprechen sich.")
        return None

    a, b = loeser.Value(x_a), loeser.Value(x_b)
    gewinn = loeser.ObjectiveValue()

    # 7. Ergebnis ausgeben
    print("=" * 58)
    print("  OPTIMALE BOT-ALLOKATION")
    print("=" * 58)
    print(f"Solver-Status:         {status_text}"
          f"{'  (beweisbar optimal)' if status == cp_model.OPTIMAL else '  (zulässig, nicht bewiesen)'}")
    print(f"Arbitrage-Bots (x_A):  {a} Instanzen")
    print(f"Trendfolge-Bots (x_B): {b} Instanzen")
    print(f"Täglicher Max-Gewinn:  {gewinn:,.2f} EUR")

    genutzt_cpu = CPU_A * a + CPU_B * b
    genutzt_ram = RAM_A * a + RAM_B * b
    print("-" * 58)
    print(f"CPU-Auslastung:        {genutzt_cpu} / {CPU_GESAMT} vCPUs "
          f"({genutzt_cpu / CPU_GESAMT * 100:.1f} %)")
    print(f"RAM-Auslastung:        {genutzt_ram} / {RAM_GESAMT} GB "
          f"({genutzt_ram / RAM_GESAMT * 100:.1f} %)")
    print(f"Arbitrage-Limit:       {a} / {MAX_ARBITRAGE}")

    # 8. Selbstkontrolle: Sind wirklich alle Bedingungen eingehalten?
    assert genutzt_cpu <= CPU_GESAMT, "vCPU-Limit verletzt!"
    assert genutzt_ram <= RAM_GESAMT, "RAM-Limit verletzt!"
    assert a <= MAX_ARBITRAGE, "Arbitrage-Limit verletzt!"

    # 9. Zum Vergleich: die naive Strategie "nur der lukrativere Bot"
    naiv_b = min(CPU_GESAMT // CPU_B, RAM_GESAMT // RAM_B)
    naiv_gewinn = GEWINN_B * naiv_b
    print("-" * 58)
    print(f"Naive Strategie (nur Trendfolge): {naiv_b} Bots -> {naiv_gewinn:,.2f} EUR")
    print(f"Vorteil der Optimierung:          {gewinn - naiv_gewinn:,.2f} EUR pro Tag "
          f"({(gewinn / naiv_gewinn - 1) * 100:.1f} %)")
    print("=" * 58)
    return a, b, gewinn


if __name__ == "__main__":
    loese_bot_allokation()
