import sqlite3
from typing import Optional, Any, List, Tuple
import logging
import threading
from .sql_utils import sanitize_identifier, quote_identifier


class Database:
    """SQLite database wrapper with connection pooling support.
    
    Thread-safe connection management with optional pre-warmed connection pools.
    """
    _pools: dict = {}  # Class-level pool storage
    _pools_lock: threading.Lock = threading.Lock()  # Thread safety for pool access

    def __init__(self, path: str, pool_size: int = 0, prewarm: bool = False) -> None:
        """Initialize database connection.
        
        Args:
            path: Path to SQLite database file
            pool_size: Size of connection pool (0 to disable pooling)
            prewarm: Whether to pre-warm the connection pool
        """
        self.path = path
        self.connection: Optional[sqlite3.Connection] = None
        self.pool_size = pool_size

        # Initialize pool if needed (thread-safe)
        if pool_size > 0:
            with Database._pools_lock:
                if path not in Database._pools:
                    Database._pools[path] = []

                    # Pre-warm pool
                    if prewarm:
                        for _ in range(pool_size):
                            try:
                                conn = sqlite3.connect(path)
                                conn.row_factory = sqlite3.Row
                                Database._pools[path].append(conn)
                            except sqlite3.Error as e:
                                logging.error(f"Failed to create connection for pool: {e}")
                                # Continue with fewer connections in the pool
                                continue

    def connect(self) -> None:
        """Connect to the database with thread-safe pool access."""
        # Use pooled connection if available (thread-safe)
        if self.pool_size > 0:
            with Database._pools_lock:
                if Database._pools[self.path]:
                    self.connection = Database._pools[self.path].pop()
                    return
        
        try:
            self.connection = sqlite3.connect(self.path)
            self.connection.row_factory = sqlite3.Row
        except sqlite3.Error as e:
            raise ConnectionError(f"Failed to connect to database at {self.path}: {e}") from e

    def close(self) -> None:
        """Close connection and return to pool or cleanup (thread-safe)."""
        if self.connection:
            try:
                self.connection.commit()
            except sqlite3.Error as e:
                logging.error(f"Failed to commit transaction: {e}")
                # Try to rollback in case of commit failure
                try:
                    self.connection.rollback()
                except sqlite3.Error as rollback_error:
                    logging.error(f"Failed to rollback transaction: {rollback_error}")
                raise

            # Return to pool or close (thread-safe)
            if self.pool_size > 0:
                with Database._pools_lock:
                    if len(Database._pools.get(self.path, [])) < self.pool_size:
                        try:
                            # Check if connection is still healthy before returning to pool
                            self.connection.execute("SELECT 1")
                            Database._pools[self.path].append(self.connection)
                        except sqlite3.Error as e:
                            logging.error(f"Connection is not healthy, discarding: {e}")
                            try:
                                self.connection.close()
                            except sqlite3.Error as close_error:
                                logging.error(f"Failed to close connection: {close_error}")
                    else:
                        try:
                            self.connection.close()
                        except sqlite3.Error as e:
                            logging.error(f"Failed to close connection: {e}")
            else:
                try:
                    self.connection.close()
                except sqlite3.Error as e:
                    logging.error(f"Failed to close connection: {e}")

            self.connection = None

    def execute(self, sql: str, params: Optional[Tuple[Any, ...]] = None) -> sqlite3.Cursor:
        """
        Execute a SQL statement with optional parameters.
        
        Args:
            sql: The SQL statement to execute
            params: Parameters to substitute in the SQL statement
            
        Returns:
            sqlite3.Cursor: Database cursor with results
            
        Raises:
            ConnectionError: If database is not connected or unhealthy
            
        Note:
            Always use parameterized queries to prevent SQL injection.
            Never concatenate user input directly into the SQL string.
        """
        if not self.connection:
            raise ConnectionError("Database not connected")
        
        # Check connection health before executing
        try:
            self.connection.execute("SELECT 1")
        except sqlite3.Error as e:
            raise ConnectionError(f"Database connection is not healthy: {e}") from e
        
        cursor = self.connection.cursor()
        if params:
            cursor.execute(sql, params)
        else:
            cursor.execute(sql)
        return cursor

    def executemany(self, sql: str, params: List[Tuple[Any, ...]]) -> sqlite3.Cursor:
        """
        Execute multiple statements with different parameters.
        
        Args:
            sql: The SQL statement to execute
            params: List of parameter tuples to substitute in the SQL statement
            
        Returns:
            sqlite3.Cursor: Database cursor with results
            
        Raises:
            ConnectionError: If database is not connected or unhealthy
            
        Note:
            Always use parameterized queries to prevent SQL injection.
            Never concatenate user input directly into the SQL string.
        """
        if not self.connection:
            raise ConnectionError("Database not connected")
        
        # Check connection health before executing
        try:
            self.connection.execute("SELECT 1")
        except sqlite3.Error as e:
            raise ConnectionError(f"Database connection is not healthy: {e}") from e
        
        cursor = self.connection.cursor()
        cursor.executemany(sql, params)
        return cursor

    def commit(self) -> None:
        if self.connection:
            self.connection.commit()

    def __enter__(self) -> 'Database':
        self.connect()
        return self

    def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_tb: Optional[Any]) -> None:
        if exc_type:
            # Rollback if an error occurred
            if self.connection:
                try:
                    self.connection.rollback()
                except sqlite3.Error as e:
                    logging.error(f"Failed to rollback transaction: {e}")
                    # Re-raise the original exception, not the rollback error
                    raise exc_val from e
        else:
            try:
                self.commit()
            except sqlite3.Error as e:
                logging.error(f"Failed to commit transaction: {e}")
                # Try to rollback in case of commit failure
                try:
                    self.connection.rollback()
                except sqlite3.Error as rollback_error:
                    logging.error(f"Failed to rollback transaction: {rollback_error}")
                raise
        self.close()

    def transaction(self) -> 'Transaction':
        """Context manager for explicit transactions"""
        return Transaction(self)


class Transaction:
    """Transaction context manager"""

    def __init__(self, database: Database) -> None:
        self.database = database

    def __enter__(self) -> 'Transaction':
        # SQLite auto-begins transactions, so we just track state
        return self

    def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_tb: Optional[Any]) -> None:
        if exc_type:
            try:
                self.database.connection.rollback()
            except sqlite3.Error as e:
                logging.error(f"Failed to rollback transaction: {e}")
                # Re-raise the original exception, not the rollback error
                raise exc_val from e
        else:
            try:
                self.database.commit()
            except sqlite3.Error as e:
                logging.error(f"Failed to commit transaction: {e}")
                # Try to rollback in case of commit failure
                try:
                    self.database.connection.rollback()
                except sqlite3.Error as rollback_error:
                    logging.error(f"Failed to rollback transaction: {rollback_error}")
                raise
        # Don't close connection, just commit/rollback
