"""SQLite storage engine for VibeDB."""

import sqlite3
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple

from vibedb.errors import DatabaseError


class StorageEngine:
    """
    SQLite storage engine with optimized settings for VibeDB.

    Features:
    - WAL (Write-Ahead Logging) mode for better concurrency
    - JSON1 extension support
    - Connection pooling (single connection with row factory)
    - Proper error handling
    """

    def __init__(self, path: str = ":memory:", timeout: float = 5.0):
        """
        Initialize storage engine.

        Args:
            path: Database file path or ":memory:" for in-memory database
            timeout: Timeout in seconds for database locks

        Raises:
            DatabaseError: If database cannot be initialized
        """
        self.path = path
        self.timeout = timeout
        self._conn: Optional[sqlite3.Connection] = None

        # Initialize connection
        self._connect()

    def _connect(self):
        """Establish database connection with optimized settings."""
        try:
            # Create parent directory if needed (skip for :memory:)
            if self.path != ":memory:":
                Path(self.path).parent.mkdir(parents=True, exist_ok=True)

            # Connect to database
            self._conn = sqlite3.connect(
                self.path,
                timeout=self.timeout,
                check_same_thread=False  # Allow multi-threaded access
            )

            # Set row factory to return dicts
            self._conn.row_factory = sqlite3.Row

            # Enable foreign keys
            self._conn.execute("PRAGMA foreign_keys = ON")

            # Enable WAL mode (better concurrency, safer)
            # Skip for :memory: databases as they don't support WAL
            if self.path != ":memory:":
                self._conn.execute("PRAGMA journal_mode = WAL")

            # Optimize SQLite performance
            self._conn.execute("PRAGMA synchronous = NORMAL")  # Faster, still safe with WAL
            self._conn.execute("PRAGMA cache_size = -64000")  # 64MB cache
            self._conn.execute("PRAGMA temp_store = MEMORY")  # Use memory for temp tables

            # Verify JSON1 extension is available
            self._check_json_support()

        except sqlite3.Error as e:
            raise DatabaseError(f"Failed to initialize database at '{self.path}'", e)

    def _check_json_support(self):
        """Verify that SQLite was compiled with JSON1 extension."""
        try:
            self._conn.execute("SELECT json('{}')").fetchone()
        except sqlite3.OperationalError:
            raise DatabaseError(
                "SQLite JSON1 extension is not available. "
                "Please upgrade SQLite to version 3.38.0 or later."
            )

    @property
    def connection(self) -> sqlite3.Connection:
        """
        Get the active database connection.

        Returns:
            sqlite3.Connection: Active database connection

        Raises:
            DatabaseError: If connection is not initialized
        """
        if self._conn is None:
            raise DatabaseError("Database connection is not initialized")
        return self._conn

    def execute(
        self,
        sql: str,
        parameters: Tuple[Any, ...] = (),
        fetch: str = None
    ) -> Any:
        """
        Execute a SQL statement.

        Args:
            sql: SQL statement to execute
            parameters: SQL parameters (use ? placeholders)
            fetch: How to fetch results: None, 'one', 'all', or 'cursor'

        Returns:
            - If fetch='one': Single row as dict or None
            - If fetch='all': List of rows as dicts
            - If fetch='cursor': Cursor object
            - Otherwise: Cursor object

        Raises:
            DatabaseError: If SQL execution fails
        """
        try:
            cursor = self.connection.execute(sql, parameters)

            if fetch == 'one':
                row = cursor.fetchone()
                return dict(row) if row else None
            elif fetch == 'all':
                return [dict(row) for row in cursor.fetchall()]
            elif fetch == 'cursor':
                return cursor
            else:
                return cursor

        except sqlite3.Error as e:
            raise DatabaseError(
                f"SQL execution failed: {sql[:100]}...",
                e
            )

    def executemany(
        self,
        sql: str,
        parameters_list: List[Tuple[Any, ...]]
    ) -> sqlite3.Cursor:
        """
        Execute a SQL statement multiple times with different parameters.

        Args:
            sql: SQL statement to execute
            parameters_list: List of parameter tuples

        Returns:
            sqlite3.Cursor: Cursor object

        Raises:
            DatabaseError: If SQL execution fails
        """
        try:
            return self.connection.executemany(sql, parameters_list)
        except sqlite3.Error as e:
            raise DatabaseError(
                f"SQL executemany failed: {sql[:100]}...",
                e
            )

    def commit(self):
        """Commit current transaction."""
        try:
            self.connection.commit()
        except sqlite3.Error as e:
            raise DatabaseError("Failed to commit transaction", e)

    def rollback(self):
        """Rollback current transaction."""
        try:
            self.connection.rollback()
        except sqlite3.Error as e:
            raise DatabaseError("Failed to rollback transaction", e)

    @contextmanager
    def transaction(self):
        """
        Context manager for transactions.

        Usage:
            with engine.transaction():
                engine.execute("INSERT ...")
                engine.execute("UPDATE ...")
            # Auto-commits on success, rolls back on exception

        Yields:
            StorageEngine: self

        Raises:
            DatabaseError: If transaction fails
        """
        try:
            yield self
            self.commit()
        except Exception:
            self.rollback()
            raise

    def close(self):
        """Close database connection."""
        if self._conn:
            try:
                self._conn.close()
                self._conn = None
            except sqlite3.Error as e:
                raise DatabaseError("Failed to close database connection", e)

    def vacuum(self):
        """
        Optimize database by rebuilding it.

        This reclaims unused space and optimizes the database file.
        Should be run periodically on production databases.

        Raises:
            DatabaseError: If vacuum fails
        """
        try:
            self.execute("VACUUM")
        except sqlite3.Error as e:
            raise DatabaseError("Failed to vacuum database", e)

    def get_stats(self) -> Dict[str, Any]:
        """
        Get database statistics.

        Returns:
            dict: Database statistics including size, page count, etc.
        """
        try:
            stats = {}

            # Get database size (in pages)
            page_count = self.execute("PRAGMA page_count", fetch='one')
            page_size = self.execute("PRAGMA page_size", fetch='one')

            if page_count and page_size:
                stats['page_count'] = page_count.get('page_count', 0)
                stats['page_size'] = page_size.get('page_size', 0)
                stats['size_bytes'] = stats['page_count'] * stats['page_size']
                stats['size_mb'] = round(stats['size_bytes'] / (1024 * 1024), 2)

            # Get journal mode
            journal = self.execute("PRAGMA journal_mode", fetch='one')
            if journal:
                stats['journal_mode'] = journal.get('journal_mode', 'unknown')

            # Get table list
            tables = self.execute(
                "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
                fetch='all'
            )
            stats['table_count'] = len(tables) if tables else 0

            return stats

        except sqlite3.Error as e:
            raise DatabaseError("Failed to get database statistics", e)

    def __enter__(self):
        """Context manager entry."""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit - closes connection."""
        self.close()

    def __del__(self):
        """Destructor - ensure connection is closed."""
        if self._conn:
            try:
                self._conn.close()
            except:
                pass  # Ignore errors during cleanup
