"""Transaction service handles transaction lifecycle management."""

from __future__ import annotations

from typing import TYPE_CHECKING, Callable, List, TypeVar

import grpc

if TYPE_CHECKING:
    from ..transaction import Transaction
    from ..types import TransactionInfo
    from .service_context import ServiceContext

T = TypeVar('T')


class TransactionService:
    """Transaction service for managing database transactions."""

    def __init__(self, ctx: ServiceContext):
        self.ctx = ctx
        self._stub = None

    def _get_stub(self):
        if self._stub is None:
            from ..proto import gqldb_pb2_grpc
            channel = self.ctx.get_channel()
            self._stub = gqldb_pb2_grpc.TransactionServiceStub(channel)
        return self._stub

    def begin_transaction(self, graph_name: str, read_only: bool, timeout: int) -> Transaction:
        from ..errors import GqldbError
        from ..proto import gqldb_pb2

        session = self.ctx.sessions.get_session()
        try:
            request = gqldb_pb2.BeginRequest(
                session_id=session.id if session else 0,
                graph_name=graph_name,
                read_only=read_only,
                timeout=timeout or self.ctx.config.timeout
            )
            metadata = self.ctx.get_session_metadata()
            response = self._get_stub().Begin(
                request, metadata=metadata, timeout=self.ctx.config.timeout
            )
            return self.ctx.tx_manager.begin(
                response.transaction_id,
                session.id if session else 0,
                graph_name,
                read_only,
                timeout or self.ctx.config.timeout
            )
        except grpc.RpcError as e:
            raise GqldbError(f"Begin transaction failed: {e.details()}") from e

    def commit(self, transaction_id: int) -> bool:
        from ..errors import GqldbError
        from ..proto import gqldb_pb2

        session = self.ctx.sessions.get_session()
        try:
            request = gqldb_pb2.CommitRequest(
                session_id=session.id if session else 0,
                transaction_id=transaction_id
            )
            metadata = self.ctx.get_session_metadata()
            response = self._get_stub().Commit(
                request, metadata=metadata, timeout=self.ctx.config.timeout
            )
            self.ctx.tx_manager.commit(transaction_id)
            return response.success
        except grpc.RpcError as e:
            raise GqldbError(f"Commit failed: {e.details()}") from e

    def rollback(self, transaction_id: int) -> bool:
        from ..errors import GqldbError
        from ..proto import gqldb_pb2

        session = self.ctx.sessions.get_session()
        try:
            request = gqldb_pb2.RollbackRequest(
                session_id=session.id if session else 0,
                transaction_id=transaction_id
            )
            metadata = self.ctx.get_session_metadata()
            response = self._get_stub().Rollback(
                request, metadata=metadata, timeout=self.ctx.config.timeout
            )
            self.ctx.tx_manager.rollback(transaction_id)
            return response.success
        except grpc.RpcError as e:
            raise GqldbError(f"Rollback failed: {e.details()}") from e

    def list_transactions(self) -> List[TransactionInfo]:
        from ..errors import GqldbError
        from ..proto import gqldb_pb2
        from ..types import TransactionInfo

        session = self.ctx.sessions.get_session()
        try:
            request = gqldb_pb2.ListTransactionsRequest(
                session_id=session.id if session else 0
            )
            metadata = self.ctx.get_session_metadata()
            response = self._get_stub().ListTransactions(
                request, metadata=metadata, timeout=self.ctx.config.timeout
            )
            return [
                TransactionInfo(
                    transaction_id=t.transaction_id,
                    session_id=t.session_id,
                    graph_name=t.graph_name,
                    read_only=t.read_only,
                    created_at=t.created_at,
                    duration_ms=t.duration_ms,
                    internal_tx_id=t.internal_tx_id
                )
                for t in response.transactions
            ]
        except grpc.RpcError as e:
            raise GqldbError(f"List transactions failed: {e.details()}") from e

    def with_transaction(
        self,
        graph_name: str,
        fn: Callable[[int], T],
        read_only: bool,
        timeout: int
    ) -> T:
        tx = self.begin_transaction(graph_name, read_only, timeout)
        try:
            result = fn(tx.id)
            self.commit(tx.id)
            return result
        except Exception as e:
            self.rollback(tx.id)
            raise e
