# SPDX-License-Identifier: LGPL-3.0-or-later

from .logging import setup_logger
from abc import ABC, abstractmethod

log = setup_logger(__name__)

class CursorContext:
    """
    Proxy wrapper around a database cursor.
    Manages cursor lifecycle as a context manager and delegates
    all attribute access to the underlying cursor object.
    """
    def __init__(self, cursor):
        self._cursor = cursor

    def __enter__(self):
        """
        Return the underlying cursor.
        """
        return self._cursor

    def __exit__(self, *args):
        """
        Close the cursor on exit.
        """
        self._cursor.close()
        return False

    def __getattr__(self, item):
        return getattr(self._cursor, item)

    def __iter__(self):
        return iter(self._cursor)

    def __next__(self):
        return next(self._cursor)

class AbstractDatabase(ABC):
    """
    Abstract base class for database connections.

    Provides a unified interface for connecting, executing queries,
    and managing transactions. Subclasses must implement _connect()
    and placeholder.

    Args:
        strict: If True, raises RuntimeError when connect() is called
                on an already open connection. If False, closes the
                existing connection silently.
    """
    def __init__(self, strict: bool = True):
        self._conn = None
        self._strict = strict

    @abstractmethod
    def _connect(self):
        """
        Establish and return a database connection.

        Must be implemented by subclasses. Read configuration from
        environment variables or any other source, then return a
        DB-API 2.0 compatible connection object.

        Returns:
            A database connection object.
        """
        pass

    def connect(self, *args, **kwargs):
        """
        Open the database connection.

        Calls _connect() internally. Should not be overridden.

        Raises:
            RuntimeError: If connection is already open and strict=True.
            RuntimeError: If _connect() returns None.
        """
        if self._conn is not None:
            if self._strict:
                raise RuntimeError("Database connection already open")
            else:
                try:
                    self._conn.close()
                except Exception as e:
                    log.error(f"Query execution failed: {e}")
                    return None

        conn = self._connect(*args, **kwargs)

        if conn is None:
            raise RuntimeError("_connect() must return a connection object")

        self._conn = conn
        return conn

    def disconnect(self):
        """
        Close the database connection and release resources.
        """
        if self._conn is not None:
            try:
                self._conn.close()
            finally:
                self._conn = None

    @property
    def connection(self):
        """
        Return the active connection object.

        Raises:
            RuntimeError: If the database is not connected.
        """
        if self._conn is None:
            raise RuntimeError("Database not connected")
        return self._conn

    @property
    @abstractmethod
    def placeholder(self) -> str:
        """
        SQL parameter placeholder for the target dialect.
        Must be implemented by subclasses.
        """
        pass

    def execute(self, sql: str, params: tuple = (), autocommit: bool = False) -> CursorContext | None:
        """
        Execute a SQL query and return a CursorContext.

        Args:
            sql: SQL query string with placeholders.
            params: Query parameters as a tuple.
            autocommit: If True, commits the transaction after execution.

        Returns:
            CursorContext wrapping the cursor, or raises on failure.

        Raises:
            Exception: Re-raises any database error after rollback.
        """
        cursor = self.connection.cursor()

        try:
            cursor.execute(sql, params)

            if autocommit:
                self.connection.commit()

            return CursorContext(cursor)

        except Exception:
            self.connection.rollback()
            log.exception("Query execution failed")
            raise

    def __enter__(self):
        """
        Support use as a context manager for connection lifecycle.
        Calls disconnect() on exit.
        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Support use as a context manager for connection lifecycle.
        Calls disconnect() on exit.
        """
        self.disconnect()