"""Hauptklasse für Questra Data."""

from __future__ import annotations

from loguru import logger
from questra_authentication import QuestraAuthentication

from .operations import (
    AuditOperations,
    DynamicInventoryOperations,
    FileOperations,
    MutationOperations,
    QueryOperations,
    TimeSeriesOperations,
)
from .rest_transport import RESTTransport
from .transport import GraphQLTransport


class QuestraDataCore:
    """
    Low-Level Client für Questra Data (Core API).

    Verwendet einen bestehenden QuestraAuthentication für die Authentifizierung
    und bietet eine typsichere Schnittstelle zur Dyno GraphQL API und REST API.

    Für die meisten Anwendungsfälle wird die High-Level API (QuestraData) empfohlen.

    Examples:
        ```python
        # QuestraAuthentication erstellen
        auth_client = QuestraAuthentication(
            url="https://authentik.dev.example.com",
            username="ServiceUser",
            password="secret_password",
            oidc_discovery_paths=["/application/o/dyno"],
        )

        # QuestraDataCore mit QuestraAuthentication initialisieren
        questra_data = QuestraDataCore(
            graphql_url="https://dyno.dev.example.com/graphql",
            rest_base_url="https://dyno.dev.example.com/",
            auth_client=auth_client,
        )

        # GraphQL: Inventories abfragen
        inventories = questra_data.queries.get_inventories()
        for inventory in inventories:
            print(f"Inventory: {inventory.name}")

        # REST: Zeitreihen-Daten abrufen
        data = questra_data.timeseries.get_data(
            time_series_ids=[123],
            from_time=datetime(2024, 1, 1),
            to_time=datetime(2024, 1, 31),
        )
        ```
    """

    def __init__(
        self,
        graphql_url: str,
        auth_client: QuestraAuthentication,
        rest_base_url: str | None = None,
    ):
        """
        Initialisiert den QuestraDataCore.

        Args:
            graphql_url: URL des GraphQL-Endpunkts
            auth_client: Konfigurierter und authentifizierter QuestraAuthentication
            rest_base_url: Basis-URL für REST-API (optional, wird aus graphql_url
                abgeleitet wenn nicht angegeben)

        Raises:
            ValueError: Wenn auth_client nicht authentifiziert ist
        """
        logger.info(
            "Initializing QuestraDataCore",
            graphql_url=graphql_url,
            rest_base_url=rest_base_url,
        )

        self._graphql_url = graphql_url
        self._auth_client = auth_client

        # Leite REST-URL aus GraphQL-URL ab, wenn nicht angegeben
        if rest_base_url is None:
            # Entferne '/graphql' vom Ende der URL
            self._rest_base_url = graphql_url.replace("/graphql", "/")
            logger.debug(
                f"REST base URL derived from GraphQL URL: {self._rest_base_url}"
            )
        else:
            self._rest_base_url = rest_base_url

        # Prüfe ob QuestraAuthentication authentifiziert ist
        if not self._auth_client.is_authenticated():
            logger.error("QuestraAuthentication is not authenticated")
            raise ValueError(
                "QuestraAuthentication ist nicht authentifiziert. "
                "Bitte authentifizieren Sie den Client, bevor Sie ihn übergeben."
            )

        logger.debug("QuestraAuthentication authentication verified")

        # Initialisiere GraphQL Transport
        self._transport = GraphQLTransport(
            url=graphql_url,
            get_access_token_func=self._auth_client.get_access_token,
        )

        # Initialisiere REST Transport
        self._rest_transport = RESTTransport(
            base_url=self._rest_base_url,
            get_access_token_func=self._auth_client.get_access_token,
        )

        # Initialisiere GraphQL Operations
        self._queries = QueryOperations(execute_func=self._transport.execute)
        self._mutations = MutationOperations(execute_func=self._transport.execute)
        self._inventory = DynamicInventoryOperations(
            execute_func=self._transport.execute
        )

        # Initialisiere REST Operations
        self._timeseries = TimeSeriesOperations(
            rest_get_func=self._rest_transport.get,
            rest_post_func=self._rest_transport.post,
        )
        self._files = FileOperations(
            rest_get_func=self._rest_transport.get,
            rest_post_func=self._rest_transport.post,
        )
        self._audit = AuditOperations(
            rest_get_func=self._rest_transport.get,
        )

        logger.info("QuestraDataCore initialized successfully")

    @property
    def queries(self) -> QueryOperations:
        """
        Zugriff auf Query-Operationen.

        Returns:
            QueryOperations: Instanz für Abfragen
        """
        return self._queries

    @property
    def mutations(self) -> MutationOperations:
        """
        Zugriff auf Mutation-Operationen.

        Returns:
            MutationOperations: Instanz für Änderungen
        """
        return self._mutations

    @property
    def inventory(self) -> DynamicInventoryOperations:
        """
        Zugriff auf dynamische Inventory-Operationen.

        Nach dem Erstellen eines Inventorys stehen automatisch
        Query- und Mutation-Operationen für die Inventory-Daten zur Verfügung.

        Returns:
            DynamicInventoryOperations: Instanz für Inventory-Daten

        Examples:
            ```python
            # Items abfragen
            result = client.inventory.list(
                inventory_name="TestUser",
                namespace_name="TestNamespace",
                properties=["_id", "Name", "Email"],
                first=10,
            )

            # Items erstellen
            items = client.inventory.create(
                inventory_name="TestUser",
                namespace_name="TestNamespace",
                items=[{"Name": "John", "Email": "john@example.com"}],
            )
            ```
        """
        return self._inventory

    @property
    def timeseries(self) -> TimeSeriesOperations:
        """
        Zugriff auf TimeSeries REST-Operationen.

        Returns:
            TimeSeriesOperations: Instanz für TimeSeries-Operationen

        Examples:
            ```python
            # Zeitreihen-Daten abrufen
            data = client.timeseries.get_data(
                time_series_ids=[123],
                from_time=datetime(2024, 1, 1),
                to_time=datetime(2024, 1, 31),
            )

            # Zeitreihen-Daten setzen
            client.timeseries.set_data([data_input])

            # Quotierungen abrufen
            quotations = client.timeseries.get_quotations(
                time_series_ids=[123],
                from_time=datetime(2024, 1, 1),
                to_time=datetime(2024, 1, 31),
            )
            ```
        """
        return self._timeseries

    @property
    def files(self) -> FileOperations:
        """
        Zugriff auf File REST-Operationen.

        Returns:
            FileOperations: Instanz für File-Operationen

        Examples:
            ```python
            # Datei hochladen
            file = client.files.upload_single(
                name="MyInventory.DocumentProperty",
                file_data="/path/to/file.pdf",
                filename="file.pdf",
                content_type="application/pdf",
            )

            # Datei herunterladen
            content = client.files.download(file_id=123)
            ```
        """
        return self._files

    @property
    def audit(self) -> AuditOperations:
        """
        Zugriff auf Audit REST-Operationen.

        Returns:
            AuditOperations: Instanz für Audit-Operationen

        Examples:
            ```python
            # TimeSeries Audit-Daten abrufen
            audit_data = client.audit.get_timeseries_data(
                time_series_id=123,
                from_time=datetime(2024, 1, 1),
                to_time=datetime(2024, 1, 31),
                audit_time=datetime(2024, 1, 15),
            )

            # File Audit-Daten abrufen
            file_audit = client.audit.get_file(file_id=123)

            # TimeSeries Metadaten abrufen
            metadata = client.audit.get_timeseries(time_series_id=123)
            ```
        """
        return self._audit

    def execute_raw(self, query: str, variables: dict | None = None) -> dict:
        """
        Führt eine rohe GraphQL-Query oder Mutation aus.

        Nützlich für benutzerdefinierte Operationen, die nicht
        durch die Standard-Operations abgedeckt sind.

        Args:
            query: GraphQL Query oder Mutation String
            variables: Optionale Variablen

        Returns:
            dict: Roh-Ergebnis der GraphQL-Operation

        Examples:
            ```python
            result = client.execute_raw('''
                query {
                    _systemInfo {
                        dynamicObjectsVersion
                    }
                }
            ''')
            ```
        """
        logger.debug("Executing raw GraphQL operation via QuestraDataCore")
        return self._transport.execute(query, variables)

    def is_authenticated(self) -> bool:
        """
        Prüft, ob der Client authentifiziert ist.

        Returns:
            bool: True wenn authentifiziert, sonst False
        """
        return self._auth_client.is_authenticated()

    def reauthenticate(self) -> None:
        """
        Erzwingt eine erneute Authentifizierung.

        Nützlich bei Authentifizierungsproblemen oder wenn
        Credentials geändert wurden.
        """
        logger.info("Forcing reauthentication")
        self._auth_client.reauthenticate()
        logger.info("Reauthentication completed")

    @property
    def graphql_url(self) -> str:
        """
        GraphQL Endpoint URL.

        Returns:
            str: URL des GraphQL-Endpunkts
        """
        return self._graphql_url

    @property
    def rest_base_url(self) -> str:
        """
        REST API Basis-URL.

        Returns:
            str: Basis-URL des REST-APIs
        """
        return self._rest_base_url

    def __repr__(self) -> str:
        """String-Repräsentation des Clients."""
        auth_status = (
            "authenticated" if self.is_authenticated() else "not authenticated"
        )
        return (
            f"QuestraDataCore(graphql_url='{self._graphql_url}', "
            f"rest_url='{self._rest_base_url}', status='{auth_status}')"
        )
