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

from __future__ import annotations

from collections.abc import Callable
from typing import Any, cast

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 = cast(
                    dict[str, Any], 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
