from abc import ABC, abstractmethod
from typing import Any, List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    from ..core.cursor import CursorWrapper


class DatabaseBackend(ABC):
    connection: Any = None

    @property
    @abstractmethod
    def placeholder_char(self) -> str:
        raise NotImplementedError

    @abstractmethod
    def connect(self, **kwargs: Any) -> None:
        raise NotImplementedError

    @abstractmethod
    def disconnect(self) -> None:
        raise NotImplementedError

    def execute(self, query: str, params: Optional[List] = None) -> "CursorWrapper":
        """Execute a query and return a CursorWrapper with the results."""
        from ..core.cursor import CursorWrapper

        if self.connection is None:
            raise RuntimeError("Database connection is not established.")
        cursor = self.connection.cursor()
        try:
            if params is None:
                cursor.execute(query)
            else:
                cursor.execute(query, params)
            return CursorWrapper(cursor)
        finally:
            cursor.close()

    @abstractmethod
    def commit(self) -> None:
        raise NotImplementedError

    @abstractmethod
    def rollback(self) -> None:
        raise NotImplementedError

    @abstractmethod
    def close(self) -> None:
        raise NotImplementedError

    @abstractmethod
    def get_field_type(self, field) -> str:
        raise NotImplementedError

    @abstractmethod
    def get_primary_key_constraint(self, column_name: str, field_type: str) -> str:
        raise NotImplementedError

    def get_check_constraint(self, column_name: str, choices: tuple) -> str:
        """Generate CHECK constraint SQL for field choices.

        Args:
            column_name: The name of the column
            choices: Tuple of allowed values

        Returns:
            CHECK constraint SQL string or empty string if not supported
        """
        if not choices:
            return ""
        # Format values for SQL (quote strings, handle None, etc.)
        formatted_choices = []
        for choice in choices:
            if choice is None:
                formatted_choices.append("NULL")
            elif isinstance(choice, str):
                # Escape single quotes in strings
                escaped = choice.replace("'", "''")
                formatted_choices.append(f"'{escaped}'")
            elif isinstance(choice, bool):
                # SQLite uses 0/1 for booleans
                formatted_choices.append("1" if choice else "0")
            else:
                formatted_choices.append(str(choice))

        choices_str = ", ".join(formatted_choices)
        return f'CHECK ("{column_name}" IN ({choices_str}))'

    @abstractmethod
    def get_tables(self, exclude_migrations: bool = False) -> list:
        """Get list of tables in database."""
        raise NotImplementedError

    @abstractmethod
    def get_table_columns(self, table_name: str) -> list:
        """Get columns for a table."""
        raise NotImplementedError

    @abstractmethod
    def prepare_value(self, value: Any) -> Any:
        """Prepare a value for insertion into the database."""
        raise NotImplementedError

    @abstractmethod
    def get_bulk_insert_ids(
        self, cursor: "CursorWrapper", num_records: int
    ) -> List[int]:
        """
        Extract inserted IDs after a bulk insert operation.

        Args:
            cursor: The cursor from the bulk insert execution
            num_records: Number of records that were inserted

        Returns:
            List of inserted IDs in order
        """
        raise NotImplementedError

    @abstractmethod
    def get_json_extract_sql(
        self, column: str, path: List[str], cast_type: str = None
    ) -> str:
        """
        Generate SQL to extract a value from a JSON Column.
        path: list of keys/indices to traverse.
        cast_type: optional SQL type to cast the result to (e.g. 'INTEGER', 'BOOLEAN').
        """
        raise NotImplementedError

    @abstractmethod
    def bulk_insert(self, sql: str, values: List[Any], num_records: int) -> List[int]:
        """
        Execute a bulk insert query and return the list of inserted IDs.

        Args:
            sql: The INSERT SQL statement
            values: List of all values for all records
            num_records: Number of records being inserted

        Returns:
            List of inserted record IDs
        """
        raise NotImplementedError
