"""Transaction management for GQLDB Python driver."""

from __future__ import annotations

import threading
import time
from dataclasses import dataclass, field
from typing import Dict, List, Optional

from .errors import TransactionNotFoundError


@dataclass
class Transaction:
    """Represents an active database transaction."""
    id: int
    session_id: int
    graph_name: str
    read_only: bool = False
    created_at: float = field(default_factory=time.time)
    timeout: float = 0.0
    _committed: bool = field(default=False, repr=False)
    _rolled_back: bool = field(default=False, repr=False)
    _lock: threading.RLock = field(default_factory=threading.RLock, repr=False)

    @property
    def is_committed(self) -> bool:
        """Check if the transaction was committed."""
        with self._lock:
            return self._committed

    @property
    def is_rolled_back(self) -> bool:
        """Check if the transaction was rolled back."""
        with self._lock:
            return self._rolled_back

    @property
    def is_active(self) -> bool:
        """Check if the transaction is still active."""
        with self._lock:
            return not self._committed and not self._rolled_back

    def age(self) -> float:
        """Return how long the transaction has been active in seconds.

        Returns:
            Transaction age in seconds.
        """
        return time.time() - self.created_at

    def is_expired(self) -> bool:
        """Check if the transaction has exceeded its timeout.

        Returns:
            True if expired.
        """
        if self.timeout == 0:
            return False
        return self.age() > self.timeout


class TransactionManager:
    """Manages transactions for the client."""

    def __init__(self):
        self._transactions: Dict[int, Transaction] = {}
        self._lock = threading.RLock()

    def begin(
        self,
        tx_id: int,
        session_id: int,
        graph_name: str,
        read_only: bool,
        timeout: float
    ) -> Transaction:
        """Create a new transaction.

        Args:
            tx_id: Transaction ID from the server.
            session_id: Session ID.
            graph_name: Graph name for the transaction.
            read_only: Whether the transaction is read-only.
            timeout: Timeout in seconds.

        Returns:
            The created Transaction.
        """
        with self._lock:
            tx = Transaction(
                id=tx_id,
                session_id=session_id,
                graph_name=graph_name,
                read_only=read_only,
                timeout=timeout
            )
            self._transactions[tx_id] = tx
            return tx

    def commit(self, tx_id: int) -> None:
        """Mark a transaction as committed.

        Args:
            tx_id: Transaction ID.

        Raises:
            TransactionNotFoundError: If transaction not found.
        """
        with self._lock:
            tx = self._transactions.get(tx_id)
            if not tx:
                raise TransactionNotFoundError()

            with tx._lock:
                tx._committed = True

            del self._transactions[tx_id]

    def rollback(self, tx_id: int) -> None:
        """Mark a transaction as rolled back.

        Args:
            tx_id: Transaction ID.

        Raises:
            TransactionNotFoundError: If transaction not found.
        """
        with self._lock:
            tx = self._transactions.get(tx_id)
            if not tx:
                raise TransactionNotFoundError()

            with tx._lock:
                tx._rolled_back = True

            del self._transactions[tx_id]

    def get(self, tx_id: int) -> Optional[Transaction]:
        """Get a transaction by ID.

        Args:
            tx_id: Transaction ID.

        Returns:
            Transaction or None.
        """
        with self._lock:
            return self._transactions.get(tx_id)

    def get_active(self) -> List[Transaction]:
        """Get all active transactions.

        Returns:
            List of active transactions.
        """
        with self._lock:
            return list(self._transactions.values())

    def get_active_for_session(self, session_id: int) -> List[Transaction]:
        """Get all active transactions for a session.

        Args:
            session_id: Session ID.

        Returns:
            List of transactions for the session.
        """
        with self._lock:
            return [
                tx for tx in self._transactions.values()
                if tx.session_id == session_id
            ]

    def has_active(self) -> bool:
        """Check if there are any active transactions.

        Returns:
            True if there are active transactions.
        """
        with self._lock:
            return len(self._transactions) > 0

    def count(self) -> int:
        """Return the number of active transactions.

        Returns:
            Number of active transactions.
        """
        with self._lock:
            return len(self._transactions)

    def clear_all(self) -> None:
        """Clear all transactions (used during logout)."""
        with self._lock:
            self._transactions.clear()
