"""Query service handles GQL query execution."""

from __future__ import annotations

import time
from typing import TYPE_CHECKING, Callable, Optional

import grpc

if TYPE_CHECKING:
    from ..client import QueryConfig
    from ..response import Response
    from .service_context import ServiceContext


class QueryService:
    """Query service for executing GQL queries."""

    def __init__(self, ctx: ServiceContext):
        """Initialize query service.

        Args:
            ctx: Service context with shared dependencies
        """
        self.ctx = ctx
        self._stub = None

    def _calculate_timeout(self, config: Optional[QueryConfig] = None) -> int:
        """Calculate timeout with the following priority:
        1. QueryConfig.timeout (highest priority) - explicitly specified timeout
        2. gRPC context deadline (medium priority) - remaining time from context deadline
        3. client.config.timeout (default) - default timeout from client config

        Args:
            config: Optional query configuration

        Returns:
            Timeout in seconds
        """
        from ..client import QueryConfig as QC
        cfg = config or QC()

        # Priority 1: Use explicitly specified timeout from QueryConfig
        if cfg.timeout and cfg.timeout > 0:
            return cfg.timeout

        # Priority 2: Try to get deadline from gRPC context (if available)
        # Note: This requires the caller to pass a context with deadline
        # For now, we skip this as Python SDK doesn't expose context parameter yet

        # Priority 3: Use default timeout from client config
        return self.ctx.config.timeout

    def _get_stub(self):
        """Get or initialize query stub."""
        # WORKAROUND: Bypass ConnectionPool for long-running queries
        # ConnectionPool has a 30-second timeout issue
        import grpc
        from ..proto import gqldb_pb2_grpc

        # Get host from config
        host = self.ctx.config.hosts[0] if self.ctx.config.hosts else "localhost:60061"

        # Create channel directly (bypassing ConnectionPool)
        # TODO: Fix ConnectionPool 30-second timeout issue
        channel = grpc.insecure_channel(host)
        return gqldb_pb2_grpc.QueryServiceStub(channel)

    def gql(self, query: str, config: Optional[QueryConfig] = None) -> Response:
        """Execute a GQL query and return the result.

        Args:
            query: GQL query string
            config: Optional query configuration

        Returns:
            Query response

        Raises:
            EmptyQueryError: If query is empty
            GqldbError: If query fails
        """
        from ..client import QueryConfig as QC
        from ..errors import EmptyQueryError, GqldbError
        from ..proto import gqldb_pb2
        from .converters import convert_gql_response

        if not query:
            raise EmptyQueryError()

        session = self.ctx.sessions.get_session()
        cfg = config or QC()
        timeout = self._calculate_timeout(config)

        try:
            # Handle graph name with priority: config > session > client config
            graph_name = cfg.graph_name
            if not graph_name:
                graph_name = self.ctx.sessions.get_default_graph()
            if not graph_name:
                graph_name = self.ctx.config.default_graph

            request = gqldb_pb2.GqlRequest(
                gql=query,
                graph_name=graph_name or '',
                session_id=session.id if session else 0,
                transaction_id=cfg.transaction_id,
                timeout=timeout,
                read_only=cfg.read_only,
                max_path_results=cfg.max_path_results
            )

            metadata = self.ctx.get_session_metadata()
            proto_response = self._get_stub().Gql(
                request,
                metadata=metadata,
                timeout=timeout
            )

            self.ctx.update_activity()
            return convert_gql_response(proto_response)
        except grpc.RpcError as e:
            raise GqldbError(f"Query failed: {e.details()}") from e

    def gql_stream(
        self,
        query: str,
        config: Optional[QueryConfig] = None,
        callback: Optional[Callable[[Response], None]] = None
    ) -> None:
        """Execute a GQL query and stream the results.

        Args:
            query: GQL query string
            config: Optional query configuration
            callback: Callback function for each response chunk

        Raises:
            EmptyQueryError: If query is empty
            GqldbError: If query fails
        """
        from ..client import QueryConfig as QC
        from ..errors import EmptyQueryError, GqldbError
        from ..proto import gqldb_pb2
        from .converters import convert_gql_response

        if not query:
            raise EmptyQueryError()

        session = self.ctx.sessions.get_session()
        cfg = config or QC()
        timeout = self._calculate_timeout(config)

        try:
            # Handle graph name with priority: config > session > client config
            graph_name = cfg.graph_name
            if not graph_name:
                graph_name = self.ctx.sessions.get_default_graph()
            if not graph_name:
                graph_name = self.ctx.config.default_graph

            request = gqldb_pb2.GqlRequest(
                gql=query,
                graph_name=graph_name or '',
                session_id=session.id if session else 0,
                transaction_id=cfg.transaction_id,
                timeout=timeout,
                read_only=cfg.read_only,
                max_path_results=cfg.max_path_results
            )

            metadata = self.ctx.get_session_metadata()
            stream = self._get_stub().GqlStream(
                request,
                metadata=metadata,
                timeout=timeout
            )

            for proto_response in stream:
                self.ctx.update_activity()
                response = convert_gql_response(proto_response)
                if callback:
                    callback(response)
        except grpc.RpcError as e:
            raise GqldbError(f"Stream query failed: {e.details()}") from e

    def explain(self, query: str, config: Optional[QueryConfig] = None) -> str:
        """Return the execution plan for a query.

        Args:
            query: GQL query string
            config: Optional query configuration

        Returns:
            Execution plan string

        Raises:
            EmptyQueryError: If query is empty
            GqldbError: If explain fails
        """
        from ..client import QueryConfig as QC
        from ..errors import EmptyQueryError, GqldbError
        from ..proto import gqldb_pb2

        if not query:
            raise EmptyQueryError()

        session = self.ctx.sessions.get_session()
        cfg = config or QC()
        timeout = self._calculate_timeout(config)

        try:
            # Handle graph name with priority: config > session > client config
            graph_name = cfg.graph_name
            if not graph_name:
                graph_name = self.ctx.sessions.get_default_graph()
            if not graph_name:
                graph_name = self.ctx.config.default_graph

            request = gqldb_pb2.GqlRequest(
                gql=query,
                graph_name=graph_name or '',
                session_id=session.id if session else 0,
                transaction_id=cfg.transaction_id,
                timeout=timeout,
                read_only=cfg.read_only,
                max_path_results=cfg.max_path_results
            )

            metadata = self.ctx.get_session_metadata()
            response = self._get_stub().Explain(
                request,
                metadata=metadata,
                timeout=timeout
            )

            return response.plan
        except grpc.RpcError as e:
            raise GqldbError(f"Explain failed: {e.details()}") from e

    def profile(self, query: str, config: Optional[QueryConfig] = None) -> str:
        """Execute a query with profiling and return statistics.

        Args:
            query: GQL query string
            config: Optional query configuration

        Returns:
            Profiling statistics string

        Raises:
            EmptyQueryError: If query is empty
            GqldbError: If profile fails
        """
        from ..client import QueryConfig as QC
        from ..errors import EmptyQueryError, GqldbError
        from ..proto import gqldb_pb2

        if not query:
            raise EmptyQueryError()

        session = self.ctx.sessions.get_session()
        cfg = config or QC()
        timeout = self._calculate_timeout(config)

        try:
            # Handle graph name with priority: config > session > client config
            graph_name = cfg.graph_name
            if not graph_name:
                graph_name = self.ctx.sessions.get_default_graph()
            if not graph_name:
                graph_name = self.ctx.config.default_graph

            request = gqldb_pb2.GqlRequest(
                gql=query,
                graph_name=graph_name or '',
                session_id=session.id if session else 0,
                transaction_id=cfg.transaction_id,
                timeout=timeout,
                read_only=cfg.read_only,
                max_path_results=cfg.max_path_results
            )

            metadata = self.ctx.get_session_metadata()
            response = self._get_stub().Profile(
                request,
                metadata=metadata,
                timeout=timeout
            )

            return response.profile
        except grpc.RpcError as e:
            raise GqldbError(f"Profile failed: {e.details()}") from e
