import inspect
from functools import wraps
from typing import TYPE_CHECKING
from typing import Any, List, Mapping

from abc import ABC, abstractmethod


CONNECTED_MESSAGE = "A connection already exists"
DISCONNECTED_MESSAGE = "No connection found to database"


def connectable(f):
    """Decorator that checks a connection does NOT exist (ready to connect)."""
    if inspect.iscoroutinefunction(f):
        @wraps(f)
        async def wrapper(*args, **kwargs):
            if args[0]._connection is not None:
                raise RuntimeError(CONNECTED_MESSAGE)
            response = await f(*args, **kwargs)
            return response
    else:
        @wraps(f)
        def wrapper(*args, **kwargs):
            if args[0]._connection is not None:
                raise RuntimeError(CONNECTED_MESSAGE)
            return f(*args, **kwargs)
    return wrapper


def disconnectable(f):
    """Decorator that checks a connection DOES exist (ready to disconnect/use)."""
    if inspect.iscoroutinefunction(f):
        @wraps(f)
        async def wrapper(*args, **kwargs):
            if args[0]._connection is None:
                raise RuntimeError(DISCONNECTED_MESSAGE)
            response = await f(*args, **kwargs)
            return response
    else:
        @wraps(f)
        def wrapper(*args, **kwargs):
            if args[0]._connection is None:
                raise RuntimeError(DISCONNECTED_MESSAGE)
            return f(*args, **kwargs)
    return wrapper


class IEngine(ABC):
    @abstractmethod
    async def connect(self) -> None:...

    @abstractmethod
    async def disconnect(self) -> None:...

    @abstractmethod
    def connection(self) -> "Connection":...

    @property
    @abstractmethod
    def parameter_placeholder(self) -> str:
        """
        Returns the parameter placeholder for the database engine.
        e.g. '$%d' for Postgres, '%s' for MySQL, '?' for SQLite
        """
        ...

    @abstractmethod
    async def execute_query(self, query: 'Query') -> Any:
        """
        Execute a query using this engine's native API.
        Handles driver-specific connection acquisition and result fetching.
        """
        ...

    async def rollback(self) -> None:
        """
        Rollback the current transaction.
        Subclasses should override this method.
        """
        raise NotImplementedError("rollback() is not implemented for this engine")


class IConnection(ABC):
    @abstractmethod
    async def begin(self) -> None:...

    @abstractmethod
    async def done(self) -> None:...

    @abstractmethod
    async def execute(self, query: 'Query') -> Any:...

    @abstractmethod
    async def executemany(self, queries: List['Query']) -> Any:...

    @abstractmethod
    async def fetchall(self, query: "Query") -> List[Mapping]:...

    @abstractmethod
    async def fetchmany(self, query: 'Query', limit: int) -> Any:...

    @abstractmethod
    async def release(self) -> None:...

    @abstractmethod
    async def rowcount(self, query: 'Query') -> int:...
