"""GraphQL Transport Layer für Questra Data."""

from __future__ import annotations

from typing import Any

from gql import Client
from gql.transport.exceptions import TransportQueryError
from gql.transport.requests import RequestsHTTPTransport
from loguru import logger

from .exceptions import QuestraGraphQLError


class GraphQLTransport:
    """
    Verwaltet die GraphQL-Transport-Schicht mit Authentifizierung.

    Verantwortlich für:
    - Erstellung und Verwaltung der GraphQL-Client-Verbindung
    - Authentifizierung über Access Token
    - Ausführung von GraphQL-Operationen
    """

    def __init__(self, url: str, get_access_token_func: callable):
        """
        Initialisiert den GraphQL Transport.

        Args:
            url: GraphQL Endpoint URL
            get_access_token_func: Funktion, die ein gültiges Access Token zurückgibt
        """
        self._url = url
        self._get_access_token = get_access_token_func
        self._client: Client | None = None
        logger.debug("GraphQL Transport initialized", url=url)

    def _create_transport(self, retries: int = 0) -> RequestsHTTPTransport:
        """
        Erstellt einen neuen HTTP Transport mit aktuellem Access Token.

        Args:
            retries: Anzahl der Wiederholungsversuche bei Fehlern (Standard: 0)

        Returns:
            Konfigurierter RequestsHTTPTransport
        """
        logger.debug(
            "Creating new HTTP transport with fresh access token", retries=retries
        )
        access_token = self._get_access_token()

        # Maskiere Token für Logging (nur erste/letzte 4 Zeichen)
        masked_token = (
            f"{access_token[:4]}...{access_token[-4:]}"
            if len(access_token) > 8
            else "***"
        )
        logger.debug("Access token retrieved", token_preview=masked_token)

        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        }

        return RequestsHTTPTransport(
            url=self._url,
            headers=headers,
            verify=True,
            retries=retries,
        )

    def get_client(self, retries: int = 0) -> Client:
        """
        Gibt den GraphQL Client zurück und erstellt ihn bei Bedarf neu.

        Args:
            retries: Anzahl der Wiederholungsversuche bei Fehlern (Standard: 0)

        Returns:
            Konfigurierter GQL Client
        """
        logger.debug("Getting GraphQL client", retries=retries)
        # Erstelle bei jedem Aufruf einen neuen Transport,
        # um sicherzustellen, dass das Token aktuell ist
        transport = self._create_transport(retries=retries)
        self._client = Client(
            transport=transport,
            fetch_schema_from_transport=False,
        )
        logger.debug("GraphQL client created successfully")
        return self._client

    def execute(
        self, query: str, variables: dict[str, Any] | None = None, retries: int = 0
    ) -> dict[str, Any]:
        """
        Führt eine GraphQL-Operation aus.

        Args:
            query: GraphQL Query oder Mutation String
            variables: Optionale Variablen für die Operation
            retries: Anzahl der Wiederholungsversuche bei Fehlern (Standard: 0)

        Returns:
            Ergebnis der GraphQL-Operation

        Raises:
            Exception: Bei GraphQL-Fehlern oder Netzwerkproblemen
        """
        from gql import gql

        # Extrahiere Operation-Namen aus Query
        operation_type = "query" if "query" in query.lower()[:50] else "mutation"
        query_preview = query[:100].replace("\n", " ").strip() + (
            "..." if len(query) > 100 else ""
        )

        logger.info(
            f"Executing GraphQL {operation_type}",
            operation_type=operation_type,
            query_preview=query_preview,
            has_variables=variables is not None,
            retries=retries,
        )
        logger.debug("GraphQL operation details", query=query, variables=variables)

        client = self.get_client(retries=retries)
        document = gql(query)

        try:
            with client as session:
                result = session.execute(document, variable_values=variables)
                logger.info(
                    f"GraphQL {operation_type} completed successfully",
                    result_keys=list(result.keys())
                    if isinstance(result, dict)
                    else None,
                )
                logger.debug("GraphQL operation result", result=result)
                return result
        except TransportQueryError as e:
            # GraphQL-Fehler vom Server (z.B. Validation, Duplicate, etc.)
            logger.warning(
                f"GraphQL {operation_type} returned errors",
                errors=e.errors,
                query_preview=query_preview,
            )

            # Extrahiere ersten Fehler (meistens gibt es nur einen)
            if e.errors:
                error = e.errors[0]
                error_message = error.get("message", "Unknown GraphQL error")
                extensions = error.get("extensions", {})
                error_code = extensions.get("code")
                placeholders = extensions.get("Placeholders", {})
                locations = error.get("locations", [])
                path = error.get("path", [])

                # Ermittle Kategorie aus messageInfos (falls verfügbar)
                # Heuristik: Kategorien sind oft im Code-Prefix (z.B. DATA_MODEL_*, VALIDATION_*)
                category = None
                if error_code and "_" in error_code:
                    category = error_code.split("_")[0].title()

                logger.debug(
                    "Raising QuestraGraphQLError",
                    code=error_code,
                    category=category,
                    placeholders=placeholders,
                )

                raise QuestraGraphQLError(
                    message=error_message,
                    code=error_code,
                    category=category,
                    placeholders=placeholders,
                    locations=locations,
                    path=path,
                    extensions=extensions,
                ) from e

            # Fallback: Keine strukturierten Fehler
            logger.error("TransportQueryError without structured errors", error=str(e))
            raise QuestraGraphQLError(
                message=str(e),
                code=None,
                category=None,
            ) from e

        except Exception as e:
            logger.error(
                f"GraphQL {operation_type} failed",
                error=str(e),
                query_preview=query_preview,
                variables=variables,
            )
            logger.exception("Full exception traceback:")
            raise
