"""
Beispiel: Notierungs-Feature - Festes vs. Freies Raster.

Demonstriert zwei Haupt-Use-Cases für Notierungen:
1. Festes Raster: Stromhandel mit Monatsprodukten
2. Freies Raster: Energieprognosen mit unregelmäßigen Updates

Voraussetzungen:
    pip install questra-data[pandas] matplotlib

Setup:
    export QUESTRA_GRAPHQL_URL="https://dev.techstack.s2o.dev/dynamic-objects-v2/"
    export QUESTRA_AUTH_URL="https://authentik.dev.techstack.s2o.dev"
    export QUESTRA_USERNAME="ServiceUser"
    export QUESTRA_PASSWORD="your_password"
"""

import os
import random
from datetime import datetime, timedelta

from questra_authentication import QuestraAuthentication

from questra_data import (
    ConflictAction,
    DataType,
    InventoryProperty,
    QuestraData,
    QuotationBehavior,
    StringPropertyConfig,
    TimeSeriesPropertyConfig,
    TimeSeriesValue,
    TimeUnit,
)
from questra_data.models.inputs import IntervalConfig

# Konfiguration aus Umgebungsvariablen
GRAPHQL_URL = os.getenv(
    "QUESTRA_GRAPHQL_URL", "https://dev.questra.s2o.dev/dynamic-objects/graphql/"
)
AUTH_URL = os.getenv("QUESTRA_AUTH_URL", "https://authentik.dev.questra.s2o.dev")
USERNAME = os.getenv("QUESTRA_USERNAME")
PASSWORD = os.getenv("QUESTRA_PASSWORD")

if not USERNAME or not PASSWORD:
    raise ValueError("QUESTRA_USERNAME and QUESTRA_PASSWORD must be set")


def setup_client() -> QuestraData:
    """Erstellt authentifizierten Client."""
    print("🔐 Authentifiziere...")
    auth_client = QuestraAuthentication(
        url=AUTH_URL,
        username=USERNAME,
        password=PASSWORD,
    )

    client = QuestraData(
        graphql_url=GRAPHQL_URL,
        auth_client=auth_client,
    )
    print("✅ Client initialisiert\n")
    return client


def scenario_a_fixed_grid(client: QuestraData):
    """
    Szenario A: Festes Raster - Stromhandel.

    - Monatliche Stromprodukte (Base, Peak)
    - Notierung = Monatsbeginn (2025-01-01, 2025-02-01, 2025-03-01)
    - 15-Minuten-Auflösung
    - Nur 24h pro Monat für Demonstration (statt 2976 Werte)
    """
    print("=" * 80)
    print("SZENARIO A: Festes Raster - Stromhandel (Monatsprodukte)")
    print("=" * 80)

    namespace_name = "Trading"
    inventory_name = "PowerProducts"

    # Schritt 1: Namespace erstellen
    print(f"\n📦 Erstelle Namespace '{namespace_name}'...")
    client.create_namespace(
        name=namespace_name,
        description="Trading Namespace für Stromprodukte",
        if_exists=ConflictAction.IGNORE,
    )

    # Schritt 2: Inventory erstellen
    print(f"📊 Erstelle Inventory '{inventory_name}'...")
    properties = [
        InventoryProperty(
            propertyName="product_name",
            dataType=DataType.STRING,
            isRequired=True,
            string=StringPropertyConfig(maxLength=100),
        ),
        InventoryProperty(
            propertyName="product_type",
            dataType=DataType.STRING,
            isRequired=True,
            string=StringPropertyConfig(maxLength=20),
        ),
        InventoryProperty(
            propertyName="delivery_period",
            dataType=DataType.STRING,
            isRequired=True,
            string=StringPropertyConfig(maxLength=20),
        ),
        InventoryProperty(
            propertyName="quarter_hourly_prices",
            dataType=DataType.TIME_SERIES,
            isRequired=False,
            timeSeries=TimeSeriesPropertyConfig(
                unit="EUR/MWh",
                timeZone="Europe/Vienna",
                interval=IntervalConfig(timeUnit=TimeUnit.MINUTE, multiplier=15),
                enableQuotation=True,  # ← Notierungs-Funktion aktiviert!
                defaultQuotationBehavior=QuotationBehavior.LATEST,
            ),
        ),
    ]

    client.create_inventory(
        name=inventory_name,
        namespace_name=namespace_name,
        properties=properties,
        description="Stromhandelsprodukte mit notierungsfähigen Preisen",
        if_exists=ConflictAction.IGNORE,
    )

    # Schritt 3: Items erstellen (3 Monatsprodukte: Jan, Feb, März Base)
    print("📝 Erstelle Monatsprodukte...")
    items = [
        {
            "product_name": "Base Januar 2025",
            "product_type": "Base",
            "delivery_period": "2025-01",
            "quarter_hourly_prices": None,  # Auto-create TimeSeries
        },
        {
            "product_name": "Base Februar 2025",
            "product_type": "Base",
            "delivery_period": "2025-02",
            "quarter_hourly_prices": None,
        },
        {
            "product_name": "Base März 2025",
            "product_type": "Base",
            "delivery_period": "2025-03",
            "quarter_hourly_prices": None,
        },
    ]

    created_items = client.create_items(
        inventory_name=inventory_name,
        namespace_name=namespace_name,
        items=items,
    )
    print(f"✅ {len(created_items)} Items erstellt")

    # Kombiniere Original-Daten mit IDs
    for original, created in zip(items, created_items):
        original["_id"] = created["_id"]
        original["_rowVersion"] = created["_rowVersion"]

    # Schritt 4: Zeitreihen-Daten speichern (nur 1 Tag pro Monat für Demo)
    print("\n💾 Speichere Notierungs-Daten (15-Min-Preise für 1 Tag)...")

    for item in items:
        delivery_period = item["delivery_period"]
        year, month = map(int, delivery_period.split("-"))

        # Notierungs-Zeitstempel = Monatsbeginn
        quotation_time = datetime(year, month, 1)

        # Generiere 15-Minuten-Preise für ersten Tag (96 Werte)
        values = []
        base_price = random.uniform(40.0, 50.0)

        for hour in range(24):
            for quarter in [0, 15, 30, 45]:
                time = datetime(year, month, 1, hour, quarter)
                # Preisschwankung: Höher tagsüber (8-20 Uhr)
                if 8 <= hour < 20:
                    price = base_price + random.uniform(5.0, 15.0)
                else:
                    price = base_price + random.uniform(-5.0, 5.0)

                values.append(TimeSeriesValue(time=time, value=round(price, 2)))

        # Speichere mit Notierungs-Zeitstempel
        client.save_quotation_values(
            inventory_name=inventory_name,
            namespace_name=namespace_name,
            timeseries_property="quarter_hourly_prices",
            item_id=str(item["_id"]),
            quotation_time=quotation_time,
            values=values,
            unit="EUR/MWh",
        )
        print(
            f"  ✅ {item['product_name']}: {len(values)} Werte mit quotation_time={quotation_time}"
        )

    # Schritt 5: Notierungen auflisten
    print("\n📋 Liste Notierungs-Zeitstempel...")
    quotations = client.list_quotation_timestamps(
        inventory_name=inventory_name,
        namespace_name=namespace_name,
        timeseries_property="quarter_hourly_prices",
        from_time=datetime(2025, 1, 1),
        to_time=datetime(2025, 3, 31, 23, 59),
        aggregated=True,
    )
    print(f"  Gefunden: {quotations}")

    # Schritt 6: Notierungs-Daten lesen
    print("\n📖 Lese Daten für Januar-Notierung...")
    data = client.list_quotation_values(
        inventory_name=inventory_name,
        namespace_name=namespace_name,
        timeseries_property="quarter_hourly_prices",
        from_time=datetime(2025, 1, 1),
        to_time=datetime(2025, 1, 1, 23, 59),
        quotation_time=datetime(2025, 1, 1),
        quotation_exactly_at=True,
        properties=["product_name", "product_type"],  # Zusätzliche Properties laden
    )

    for item_id, item_data in data.items():
        item = item_data["item"]
        ts_data = item_data["timeseries"]["quarter_hourly_prices"]
        print(f"  {item['product_name']}: {len(ts_data['values'])} Werte")
        # Zeige erste 3 Werte
        for val in ts_data["values"][:3]:
            print(f"    {val.time}: {val.value} EUR/MWh")

    # Schritt 7: Notierungen vergleichen
    print("\n📊 Vergleiche Notierungen (Q1 Monate)...")
    try:
        df = client.compare_quotations(
            inventory_name=inventory_name,
            namespace_name=namespace_name,
            timeseries_property="quarter_hourly_prices",
            item_id=str(items[0]["_id"]),
            quotation_times=[
                datetime(2025, 1, 1),
                datetime(2025, 2, 1),
                datetime(2025, 3, 1),
            ],
            from_time=datetime(2025, 1, 1),
            to_time=datetime(2025, 3, 31, 23, 59),
        )
        print(f"  DataFrame: {df.shape} (Zeilen x Spalten)")
        print(f"\n{df.head()}")

        # Durchschnittspreise pro Notierung
        value_cols = df.xs("value", level=1, axis=1)
        avg_prices = value_cols.mean()
        print("\n  Durchschnittspreise:")
        for quot_time, avg_price in avg_prices.items():
            print(f"    {quot_time}: {avg_price:.2f} EUR/MWh")

    except ImportError:
        print("  ⚠️  pandas nicht installiert, überspringe DataFrame-Vergleich")

    print("\n✅ Szenario A abgeschlossen\n")


def scenario_b_flexible_grid(client: QuestraData):
    """
    Szenario B: Freies Raster - Energieprognosen.

    - Tägliche Load-Forecasts für Regionen
    - Notierung = Forecast-Erstellungszeitpunkt (unregelmäßig: 6, 12, 18 Uhr)
    - 1-Stunden-Auflösung
    - 7-Tage-Forecast (168 Werte)
    """
    print("=" * 80)
    print("SZENARIO B: Freies Raster - Energieprognosen (Rolling Forecasts)")
    print("=" * 80)

    namespace_name = "Operations"
    inventory_name = "EnergyForecasts"

    # Schritt 1: Namespace erstellen
    print(f"\n📦 Erstelle Namespace '{namespace_name}'...")
    client.create_namespace(
        name=namespace_name,
        description="Operations Namespace für Energieprognosen",
        if_exists=ConflictAction.IGNORE,
    )

    # Schritt 2: Inventory erstellen
    print(f"📊 Erstelle Inventory '{inventory_name}'...")
    properties = [
        InventoryProperty(
            propertyName="region_name",
            dataType=DataType.STRING,
            isRequired=True,
            string=StringPropertyConfig(maxLength=100),
        ),
        InventoryProperty(
            propertyName="region_code",
            dataType=DataType.STRING,
            isRequired=True,
            string=StringPropertyConfig(maxLength=10),
        ),
        InventoryProperty(
            propertyName="predicted_load",
            dataType=DataType.TIME_SERIES,
            isRequired=False,
            timeSeries=TimeSeriesPropertyConfig(
                unit="MW",
                timeZone="Europe/Vienna",
                interval=IntervalConfig(timeUnit=TimeUnit.HOUR, multiplier=1),
                enableQuotation=True,  # ← Notierungs-Funktion aktiviert!
                defaultQuotationBehavior=QuotationBehavior.LATEST_NO_FUTURE,
            ),
        ),
    ]

    client.create_inventory(
        name=inventory_name,
        namespace_name=namespace_name,
        properties=properties,
        description="Energieprognosen mit notierungsfähigen Load-Forecasts",
        if_exists=ConflictAction.IGNORE,
    )

    # Schritt 3: Items erstellen (3 Regionen)
    print("📝 Erstelle Regionen...")
    items = [
        {
            "region_name": "Region Nord",
            "region_code": "NORD",
            "predicted_load": None,  # Auto-create TimeSeries
        },
        {
            "region_name": "Region Süd",
            "region_code": "SUED",
            "predicted_load": None,
        },
        {
            "region_name": "Region West",
            "region_code": "WEST",
            "predicted_load": None,
        },
    ]

    created_items = client.create_items(
        inventory_name=inventory_name,
        namespace_name=namespace_name,
        items=items,
    )
    print(f"✅ {len(created_items)} Items erstellt")

    # Kombiniere Original-Daten mit IDs
    for original, created in zip(items, created_items):
        original["_id"] = created["_id"]
        original["_rowVersion"] = created["_rowVersion"]

    # Schritt 4: Zeitreihen-Daten speichern (3 Forecasts über 3 Tage)
    print("\n💾 Speichere Notierungs-Daten (3 Forecast-Updates)...")

    # Basis-Datum
    base_date = datetime(2025, 1, 10)

    # 3 Forecast-Updates: Tag -2, -1, 0 (jeweils 6 Uhr)
    forecast_updates = [
        base_date - timedelta(days=2) + timedelta(hours=6),  # 2025-01-08 06:00
        base_date - timedelta(days=1) + timedelta(hours=6),  # 2025-01-09 06:00
        base_date + timedelta(hours=6),  # 2025-01-10 06:00
    ]

    for item in items:
        base_load = random.uniform(100.0, 200.0)

        for forecast_idx, quotation_time in enumerate(forecast_updates):
            # Generiere 24h Forecast (vereinfacht, statt 7 Tage)
            values = []

            for hour in range(24):
                time = base_date + timedelta(hours=hour)

                # Forecast wird genauer je näher am Zeitpunkt
                # Füge Noise hinzu: mehr bei älteren Forecasts
                noise_factor = (3 - forecast_idx) * 5.0
                load = base_load + random.uniform(-noise_factor, noise_factor)

                # Tagesmuster: Höher tagsüber
                if 8 <= hour < 20:
                    load += 30.0

                values.append(TimeSeriesValue(time=time, value=round(load, 1)))

            # Speichere mit Notierungs-Zeitstempel
            client.save_quotation_values(
                inventory_name=inventory_name,
                namespace_name=namespace_name,
                timeseries_property="predicted_load",
                item_id=str(item["_id"]),
                quotation_time=quotation_time,
                values=values,
                unit="MW",
            )
            print(
                f"  ✅ {item['region_name']}: Forecast #{forecast_idx + 1} "
                f"mit quotation_time={quotation_time}"
            )

    # Schritt 5: Notierungen auflisten
    print("\n📋 Liste Notierungs-Zeitstempel (aggregiert)...")
    quotations = client.list_quotation_timestamps(
        inventory_name=inventory_name,
        namespace_name=namespace_name,
        timeseries_property="predicted_load",
        from_time=base_date,
        to_time=base_date + timedelta(days=1),
        aggregated=True,
    )
    print(f"  Gefunden: {quotations}")

    # Schritt 6: Neuester Forecast lesen
    print("\n📖 Lese neuesten Forecast (LATEST_NO_FUTURE)...")
    data = client.list_quotation_values(
        inventory_name=inventory_name,
        namespace_name=namespace_name,
        timeseries_property="predicted_load",
        from_time=base_date,
        to_time=base_date + timedelta(hours=23, minutes=59),
        quotation_behavior=QuotationBehavior.LATEST_NO_FUTURE,
        properties=["region_name", "region_code"],  # Zusätzliche Properties laden
    )

    for item_id, item_data in data.items():
        item = item_data["item"]
        ts_data = item_data["timeseries"]["predicted_load"]
        print(f"  {item['region_name']}: {len(ts_data['values'])} Werte")
        # Zeige erste 3 Werte
        for val in ts_data["values"][:3]:
            print(f"    {val.time}: {val.value} MW")

    # Schritt 7: Forecasts vergleichen (Accuracy-Analyse der Notierungen)
    print("\n📊 Vergleiche Forecasts (Wie hat sich Prognose entwickelt?)...")
    try:
        df = client.compare_quotations(
            inventory_name=inventory_name,
            namespace_name=namespace_name,
            timeseries_property="predicted_load",
            item_id=str(items[0]["_id"]),
            quotation_times=forecast_updates,
            from_time=base_date,
            to_time=base_date + timedelta(hours=23, minutes=59),
        )
        print(f"  DataFrame: {df.shape} (Zeilen x Spalten)")
        print(f"\n{df.head()}")

        # Durchschnittswerte pro Forecast
        value_cols = df.xs("value", level=1, axis=1)
        avg_loads = value_cols.mean()
        print("\n  Durchschnittliche Last-Prognosen:")
        for quot_time, avg_load in avg_loads.items():
            days_before = (base_date + timedelta(hours=6) - quot_time).days
            print(f"    {quot_time} ({days_before} Tage vorher): {avg_load:.1f} MW")

        # Änderungen zwischen Forecasts
        forecast_diff = value_cols.diff(axis=1)
        print("\n  Durchschnittliche Änderungen zwischen Forecasts:")
        for col in forecast_diff.columns[1:]:
            avg_change = forecast_diff[col].abs().mean()
            print(f"    -> {col}: {avg_change:.1f} MW")

    except ImportError:
        print("  ⚠️  pandas nicht installiert, überspringe DataFrame-Vergleich")

    print("\n✅ Szenario B abgeschlossen\n")


def main():
    """Hauptfunktion - Führt beide Szenarien aus."""
    print("\n" + "=" * 80)
    print("NOTIERUNGS-FEATURE DEMONSTRATION")
    print("=" * 80)
    print("\nDemonstriert zwei Use-Cases:")
    print("  A) Festes Raster - Stromhandel mit Monatsprodukten")
    print("  B) Freies Raster - Energieprognosen mit Rolling Updates")
    print("\n" + "=" * 80 + "\n")

    client = setup_client()

    # Szenario A: Festes Raster
    scenario_a_fixed_grid(client)

    # Szenario B: Freies Raster
    scenario_b_flexible_grid(client)

    print("=" * 80)
    print("✅ ALLE SZENARIEN ABGESCHLOSSEN")
    print("=" * 80)
    print("\nNächste Schritte:")
    print("  - Explore Daten via UI oder API")
    print("  - Teste weitere Notierungs-Behaviors (QuotationBehavior)")
    print("  - Analysiere Forecast-Accuracy mit pandas/matplotlib")


if __name__ == "__main__":
    main()
