"""
Beispiel für die Verwendung der High-Level API of Questra Data.

Die High-Level API abstrahiert die Komplexität of GraphQL and REST
und bietet eine benutzerfreundliche Schnittstelle für häufige Operationen.

Der Benutzer muss nicht wissen, ob intern GraphQL or REST verwendet wird.
"""

from __future__ import annotations

import os
from datetime import datetime, timedelta
from typing import Any, cast

from seven2one.questra.authentication import QuestraAuthentication

from seven2one.questra.data import Quality, QuestraData, TimeSeriesValue


def main():
    """Hauptfunktion with High-Level API Beispielen."""

    # ===== Client initialisieren =====
    print("=" * 60)
    print("High-Level API - Client initialisieren")
    print("=" * 60)

    # 1. QuestraAuthentication erstellen
    auth_client = QuestraAuthentication(
        url="https://authentik.dev.techstack.s2o.dev",
        username=os.getenv("DATA_USERNAME", "ServiceUser"),
        password=os.getenv("DATA_PASSWORD", "secret"),
    )

    # 2. QuestraData with QuestraAuthentication initialisieren
    client = QuestraData(
        graphql_url="https://dev.techstack.s2o.dev/dynamic-objects-v2/graphql",
        auth_client=auth_client,
    )

    print(f"Client initialisiert: {client}")
    print(f"Authentifiziert: {client.is_authenticated()}")
    print()

    # ===== System-Informationen =====
    print("=" * 60)
    print("System-Informationen abrufen")
    print("=" * 60)

    info = client.get_system_info()
    print(f"Data Version: {info.dynamic_objects_version}")
    print(f"Datenbank: {info.database_version}")
    print()

    # ===== Namespaces and Inventories auflisten =====
    print("=" * 60)
    print("Namespaces and Inventories auflisten")
    print("=" * 60)

    namespaces = client.list_namespaces()
    print(f"Anzahl Namespaces: {len(namespaces)}")
    for ns in namespaces[:5]:  # Nur erste 5
        print(f"  - {ns.name}: {ns.description or 'Keine Beschreibung'}")
    print()

    inventories = client.list_inventories(namespace_name="TestNamespace")
    print(f"Inventories in 'TestNamespace': {len(inventories)}")
    for inv in inventories[:5]:  # Nur erste 5
        inv_type = inv.inventory_type.value if inv.inventory_type else "unknown"
        print(f"  - {inv.name} ({inv_type})")
    print()

    # Roles auflisten
    roles = client.list_roles()
    print(f"Anzahl Roles: {len(roles)}")
    for role in roles[:5]:  # Nur erste 5
        print(f"  - {role.name}: {role.description or 'Keine Beschreibung'}")
    print()

    # Units auflisten
    units = client.list_units()
    print(f"Anzahl Units: {len(units)}")
    for unit in units[:10]:  # Nur erste 10
        print(f"  - {unit.symbol}: {unit.aggregation}")
    print()

    # Zeitzonen auflisten
    time_zones = client.list_time_zones()
    print(f"Anzahl Zeitzonen: {len(time_zones)}")
    for tz in time_zones[:5]:  # Nur erste 5
        print(f"  - {tz.name}: UTC{tz.base_utc_offset}")
    print()

    # ===== Namespace and Inventory erstellen =====
    print("=" * 60)
    print("Namespace and Inventory erstellen")
    print("=" * 60)

    # Namespace erstellen (wird ignoriert wenn schon existiert)
    ns_result = client.create_namespace(
        name="Energie", description="Namespace für Energie-Management"
    )
    print(
        f"Namespace erstellt: {ns_result.name}, existierte bereits: {ns_result.existed}"
    )

    # Inventory für Stromzähler erstellen (mit spezialisierten Property-Klassen - recommended)
    from seven2one.questra.data import StringProperty, TimeSeriesProperty, TimeUnit

    properties = [
        StringProperty(
            propertyName="stromzaehlernummer",
            maxLength=50,
            isRequired=True,
            isUnique=True,
        ),
        StringProperty(propertyName="hersteller", maxLength=100, isRequired=False),
        TimeSeriesProperty(
            propertyName="messwerte_Energie",
            timeUnit=TimeUnit.HOUR,
            multiplier=1,
            unit="kWh",
            isRequired=False,
        ),
    ]

    inv_result = client.create_inventory(
        name="Stromzaehler",
        namespace_name="Energie",
        properties=cast(Any, properties),  # Type variance workaround
        description="Inventory für Stromzähler with Messwerten",
    )
    print(
        f"Inventory erstellt: {inv_result.name}, existierte bereits: {inv_result.existed}"
    )

    # Beispiel: Inventory with Relationen erstellen
    # Erst Gebäude-Inventory erstellen
    from seven2one.questra.data import InventoryRelation, RelationType

    building_properties = [
        StringProperty(
            propertyName="gebaeudename", maxLength=100, isRequired=True, isUnique=True
        )
    ]

    building_result = client.create_inventory(
        name="Gebaeude",
        namespace_name="Energie",
        properties=cast(Any, building_properties),  # Type variance workaround
        description="Gebäude-Inventory",
    )
    print(
        f"Gebäude-Inventory erstellt: {building_result.name}, existierte bereits: {building_result.existed}"
    )

    # Dann Raum-Inventory with Relation to Gebäude erstellen
    room_properties = [
        StringProperty(propertyName="raumnummer", maxLength=50, isRequired=True)
    ]

    relations = [
        InventoryRelation(
            propertyName="Gebaeude",
            relationType=RelationType.ONE_TO_MANY,
            parentInventoryName="Gebaeude",
            parentPropertyName="Raeume",
        )
    ]

    room_result = client.create_inventory(
        name="Raeume",
        namespace_name="Energie",
        properties=cast(Any, room_properties),  # Type variance workaround
        relations=relations,
        description="Räume-Inventory with Gebäude-Relation",
    )
    print(
        f"Raum-Inventory with Relation erstellt: {room_result.name}, existierte bereits: {room_result.existed}"
    )
    print()

    # ===== Inventory löschen =====
    print("=" * 60)
    print("Inventory löschen")
    print("=" * 60)

    # Erstelle Test-Inventory zum Löschen
    test_props = [
        StringProperty(propertyName="testfeld", maxLength=50, isRequired=False)
    ]

    test_inv_result = client.create_inventory(
        name="TestInventoryToDelete",
        namespace_name="Energie",
        properties=cast(Any, test_props),  # Type variance workaround
        description="Test-Inventory zum Löschen",
    )
    print(f"Test-Inventory erstellt: {test_inv_result.name}")

    # Inventory löschen
    delete_result = client.delete_inventory(
        inventory_name="TestInventoryToDelete", namespace_name="Energie"
    )
    print(
        f"Inventory gelöscht: {delete_result.name}, existierte: {delete_result.existed}"
    )
    print()

    # ===== Inventory Items laden and speichern =====
    print("=" * 60)
    print("Inventory Items laden and speichern")
    print("=" * 60)

    # Items erstellen
    # WICHTIG: Bei Properties vom DataType.timeSERIES wird automatisch eine Zeitreihe angelegt!
    # Dazu muss die Property im Item as None angegeben werden
    items = [
        {
            "stromzaehlernummer": "SZ-12345",
            "hersteller": "Siemens",
            "messwerte_Energie": None,  # Zeitreihe wird automatisch angelegt
        },
        {
            "stromzaehlernummer": "SZ-67890",
            "hersteller": "ABB",
            "messwerte_Energie": None,  # Zeitreihe wird automatisch angelegt
        },
    ]

    created_items = client.create_items(
        inventory_name="Stromzaehler", namespace_name="Energie", items=items
    )
    print(
        f"{len(created_items)} Stromzähler erstellt (mit automatischer TimeSeries-Erstellung)"
    )
    for item in created_items:
        status = "already vorhanden" if item.get("_existed") else "neu"
        print(f"  - ID: {item['_id']}, {status}")
    print()

    # Items auflisten
    loaded_items = client.list_items(
        inventory_name="Stromzaehler",
        namespace_name="Energie",
        properties=["_id", "_rowVersion", "stromzaehlernummer", "hersteller"],
        limit=10,
    )
    print(f"{len(loaded_items)} Stromzähler aufgelistet:")
    for item in loaded_items:
        print(f"  - {item['stromzaehlernummer']} ({item['hersteller']})")
    print()

    # With Filter auflisten
    filtered_items = client.list_items(
        inventory_name="Stromzaehler",
        namespace_name="Energie",
        properties=["_id", "stromzaehlernummer", "hersteller"],
        where={"stromzaehlernummer": {"eq": "SZ-12345"}},
        limit=10,
    )
    print(f"Gefilterte Stromzähler: {len(filtered_items)}")
    for item in filtered_items:
        print(f"  - {item['stromzaehlernummer']} ({item['hersteller']})")
    print()

    # ===== Zeitreihenvalues laden (HIGH-LEVEL!) =====
    print("=" * 60)
    print("Zeitreihenvalues laden (High-Level)")
    print("=" * 60)

    # Dies ist der große Vorteil: Ein einziger Aufruf!
    # Der Benutzer muss nicht:
    # 1. Items with TimeSeries-Property laden
    # 2. TimeSeries-IDs extrahieren
    # 3. REST-Call für Zeitreihen-Daten machen
    # Das macht alles die High-Level API automatisch!

    try:
        result = client.list_timeseries_values(
            inventory_name="Stromzaehler",
            namespace_name="Energie",
            timeseries_properties="messwerte_Energie",
            from_time=datetime(2025, 1, 1),
            to_time=datetime(2025, 12, 31),
        )

        print(f"Zeitreihenvalues für {len(result)} Stromzähler aufgelistet:")
        for item_id, data in result.items():
            item = data["item"]
            ts_data = data["timeseries"]["messwerte_Energie"]
            values = ts_data["values"]
            print(f"\n  Stromzähler: {item.get('stromzaehlernummer', 'N/A')}")
            print(f"  TimeSeries ID: {ts_data['timeseries_id']}")
            print(f"  Unit: {ts_data['unit']}, Interval: {ts_data['interval']}")
            print(f"  Anzahl Werte: {len(values)}")
            if values:
                print(f"  Erster Wert: {values[0].time} = {values[0].value}")
                print(f"  Letzter Wert: {values[-1].time} = {values[-1].value}")

    except Exception as e:
        print(f"Hinweis: {e}")
        print(
            "(Keine Zeitreihen-Daten vorhanden - das ist normal wenn noch keine Daten eingefügt wurden)"
        )
    print()

    # ===== Zeitreihenvalues speichern (HIGH-LEVEL!) =====
    print("=" * 60)
    print("Zeitreihenvalues speichern (High-Level)")
    print("=" * 60)

    # Erstelle Beispielwerte
    try:
        # Zuerst Items auflisten um IDs to bekommen
        items = client.list_items(
            inventory_name="Stromzaehler",
            namespace_name="Energie",
            properties=["_id", "stromzaehlernummer"],
            limit=2,
        )

        if items:
            # Erstelle Zeitreihenvalues für die ersten 2 Stromzähler
            now = datetime.now()
            item_values = {}

            for i, item in enumerate(items[:2]):
                values = [
                    TimeSeriesValue(
                        time=now - timedelta(hours=j),
                        value=100.0 + i * 10 + j * 0.5,
                        quality=Quality.VALID,
                    )
                    for j in range(5)
                ]
                item_values[item["_id"]] = values

            # Ein einziger Aufruf zum Speichern!
            # Der Benutzer muss nicht:
            # 1. Items with TimeSeries-Property laden
            # 2. TimeSeries-IDs extrahieren
            # 3. SetTimeSeriesDataInput erstellen
            # 4. REST-Call machen
            # Das macht alles die High-Level API automatisch!

            from seven2one.questra.data import TimeUnit

            # Option 1: Bulk-Methode für mehrere Items
            client.save_timeseries_values_bulk(
                inventory_name="Stromzaehler",
                namespace_name="Energie",
                timeseries_properties="messwerte_Energie",
                item_values=item_values,
                time_unit=TimeUnit.HOUR,
                multiplier=1,
                unit="kWh",
            )

            print(
                f"Zeitreihenvalues für {len(item_values)} Stromzähler gespeichert (Bulk)"
            )
            for item_id, values in item_values.items():
                print(f"  - Item ID {item_id}: {len(values)} Werte")

            # Option 2: Einfache Methode für ein einzelnes Item
            # (Für den häufigsten Use-Case)
            first_item_id = list(item_values.keys())[0]
            first_values = item_values[first_item_id]

            client.save_timeseries_values(
                inventory_name="Stromzaehler",
                namespace_name="Energie",
                timeseries_property="messwerte_Energie",
                item_id=first_item_id,
                values=first_values,
                time_unit=TimeUnit.HOUR,
                multiplier=1,
                unit="kWh",
            )

            print("\nZeitreihenvalues für einen Stromzähler gespeichert (Einfach)")
            print(f"  - Item ID {first_item_id}: {len(first_values)} Werte")

    except Exception as e:
        print(f"Hinweis: {e}")
        print(
            "(Fehler beim Speichern - möglicherweise müssen TimeSeries erst erstellt werden)"
        )
    print()

    # ===== Low-Level API Zugriff (für fortgeschrittene Operationen) =====
    print("=" * 60)
    print("Low-Level API Zugriff")
    print("=" * 60)

    # Für spezielle Operationen kann der Low-Level Client verwendet werden
    result = client.lowlevel.execute_raw("""
        query {
            _timeZones(first: 5) {
                name
                baseUtcOffset
            }
        }
    """)

    print("Erste 5 Zeitzonen (via Low-Level API):")
    for tz in result["_timeZones"]:
        print(f"  - {tz['name']}: {tz['baseUtcOffset']}")
    print()

    # ===== Pandas DataFrame Beispiele (Optional) =====
    try:
        import pandas as pd  # noqa: F401

        pandas_available = True
    except ImportError:
        pandas_available = False

    if pandas_available:
        print("=" * 60)
        print("Pandas DataFrame Integration")
        print("=" * 60)

        # Zeitreihen as DataFrame
        print("Zeitreihen-DataFrame:")
        ts_result = client.list_timeseries_values(
            inventory_name="Stromzaehler",
            namespace_name="Energie",
            timeseries_properties="messwerte_Energie",
            from_time=datetime.now() - timedelta(days=7),
            to_time=datetime.now(),
            properties=["stromzaehlernummer"],
        )
        df_timeseries = ts_result.to_df(properties=["stromzaehlernummer"])
        print(df_timeseries.head())
        print()

        # Inventory Items as DataFrame
        print("Inventory Items as DataFrame:")
        items = client.list_items(
            inventory_name="Stromzaehler",
            namespace_name="Energie",
            properties=["_id", "_rowVersion", "stromzaehlernummer", "hersteller"],
            limit=10,
        )
        df_items = items.to_df()

        print(f"DataFrame with {len(df_items)} Items:")
        if not df_items.empty:
            print(df_items)
        print()

        # Inventories as DataFrame
        print("Inventories as DataFrame:")
        inventories = client.list_inventories(namespace_name="Energie")
        df_inventories = inventories.to_df()

        print(f"DataFrame with {len(df_inventories)} Inventories:")
        if not df_inventories.empty:
            print(df_inventories[["name", "namespace_name", "inventory_type"]].head())
        print()

        # Roles as DataFrame
        print("Roles as DataFrame:")
        roles = client.list_roles()
        df_roles = roles.to_df()

        print(f"DataFrame with {len(df_roles)} Roles:")
        if not df_roles.empty:
            print(df_roles[["name", "description", "is_system"]].head(10))

            # System vs. Custom Roles
            print(f"\nSystem-Roles: {len(df_roles[df_roles['is_system']])}")
            print(f"Custom-Roles: {len(df_roles[~df_roles['is_system']])}")
        print()

        # Units as DataFrame
        print("Units as DataFrame:")
        units = client.list_units()
        df_units = units.to_df()

        print(f"DataFrame with {len(df_units)} Units:")
        if not df_units.empty:
            print(df_units.head(15))

            # Aggregationsarten gruppieren
            print("\nUnits nach Aggregationsart:")
            print(df_units.groupby("aggregation").size())
        print()

        # Zeitzonen as DataFrame
        print("Zeitzonen as DataFrame:")
        time_zones = client.list_time_zones()
        df_tz = time_zones.to_df()

        print(f"DataFrame with {len(df_tz)} Zeitzonen:")
        if not df_tz.empty:
            print(df_tz.head(10))

            # Zeitzonen with Sommerzeit
            print(
                f"\nZeitzonen with Sommerzeit: {len(df_tz[df_tz['supports_daylight_saving_time']])}"
            )

            # Europäische Zeitzonen
            europe_tz = df_tz[df_tz["name"].str.contains("Europe")]
            print(f"Europäische Zeitzonen: {len(europe_tz)}")
            if not europe_tz.empty:
                print(europe_tz.head())
        print()

    else:
        print("=" * 60)
        print("Pandas nicht installiert")
        print("=" * 60)
        print("Für DataFrame-Unterstützung installieren mit:")
        print("  pip install seven2one-questra-data[pandas]")
        print()

    print("=" * 60)
    print("High-Level API Beispiele abgeschlossen!")
    print("=" * 60)


def simple_example():
    """
    Sehr einfaches Beispiel, das den Hauptvorteil der High-Level API zeigt.
    """
    print("\n\n")
    print("=" * 60)
    print("EINFACHES BEISPIEL - Vorher vs. Nachher")
    print("=" * 60)

    # Initialisierung
    auth_client = QuestraAuthentication(
        url="https://authentik.dev.techstack.s2o.dev",
        username=os.getenv("DYNO_USERNAME", "ServiceUser"),
        password=os.getenv("DYNO_PASSWORD", "secret"),
    )

    QuestraData(
        graphql_url="https://dev.techstack.s2o.dev/dynamic-objects-v2/graphql",
        auth_client=auth_client,
    )

    print("\n>>> VORHER (Low-Level API - 3 Schritte) <<<\n")
    print("""
    # Schritt 1: Items with TimeSeries-Property laden
    result = client.inventory.list(
        inventory_name="Stromzaehler",
        namespace_name="Energie",
        properties=["_id", "messwerte_Energie.id"]
    )

    # Schritt 2: TimeSeries-IDs extrahieren
    timeseries_ids = [
        item["messwerte_Energie"]["id"]
        for item in result["nodes"]
        if item.get("messwerte_Energie")
    ]

    # Schritt 3: Zeitreihen-Daten laden
    data = client.timeseries.get_data(
        time_series_ids=timeseries_ids,
        from_time=datetime(2025, 1, 1),
        to_time=datetime(2025, 12, 31)
    )
    """)

    print("\n>>> NACHHER (High-Level API - 1 Schritt!) <<<\n")
    print("""
    # Nur ein einziger Aufruf!
    result = client.list_timeseries_values(
        inventory_name="Stromzaehler",
        namespace_name="Energie",
        timeseries_property="messwerte_Energie",
        from_time=datetime(2025, 1, 1),
        to_time=datetime(2025, 12, 31)
    )
    """)

    print("\n>>> ERGEBNIS <<<\n")
    print("✓ Der Benutzer muss nicht verstehen, like GraphQL and REST zusammenspielen")
    print(
        "✓ Der Benutzer muss nicht wissen, dass TimeSeries über Properties referenziert werden"
    )
    print(
        "✓ Der Benutzer bekommt ein strukturiertes Dictionary with allen relevanten Daten"
    )
    print("✓ Der Code ist viel kürzer and lesbarer")
    print()


if __name__ == "__main__":
    print("Questra Data - High-Level API Beispiele\n")

    # Hauptbeispiele
    main()

    # Einfaches Vergleichsbeispiel
    simple_example()
