"""GraphQL Transport Layer for Questra Automation."""

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 QuestraAutomationGraphQLError


class GraphQLTransport:
    """
    Manages the GraphQL transport layer with authentication.

    Responsible for:
    - Creating and managing the GraphQL client connection
    - Authentication via access token
    - Executing GraphQL operations
    """

    def __init__(self, url: str, get_access_token_func: callable):
        """
        Initialize the GraphQL transport.

        Args:
            url: GraphQL endpoint URL
            get_access_token_func: Function that returns a valid access token
        """
        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:
        """
        Create a new HTTP transport with current access token.

        Args:
            retries: Number of retry attempts on errors (default: 0)

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

        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:
        """
        Return the GraphQL client, creating it if needed.

        Args:
            retries: Number of retry attempts on errors (default: 0)

        Returns:
            Configured GQL client
        """
        logger.debug("Getting GraphQL client", retries=retries)
        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]:
        """
        Execute a GraphQL operation.

        Args:
            query: GraphQL query or mutation string
            variables: Optional variables for the operation
            retries: Number of retry attempts on errors (default: 0)

        Returns:
            Result of the GraphQL operation

        Raises:
            QuestraAutomationGraphQLError: On GraphQL errors or network problems
        """
        from gql import gql

        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:
            logger.warning(
                f"GraphQL {operation_type} returned errors",
                errors=e.errors,
                query_preview=query_preview,
            )

            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", [])

                category = None
                if error_code and "_" in error_code:
                    category = error_code.split("_")[0].title()

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

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

            logger.error("TransportQueryError without structured errors", error=str(e))
            raise QuestraAutomationGraphQLError(
                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
