"""Index management for VibeDB - create, drop, and list indexes."""

from typing import List, Dict, Any, TYPE_CHECKING
import hashlib

from vibedb.errors import IndexError as VibeIndexError
from vibedb.utils import current_timestamp, format_json_path, safe_field_name

if TYPE_CHECKING:
    from vibedb.core.collection import Collection


class IndexManager:
    """
    Manages indexes for a collection.

    Creates SQLite expression indexes on JSON fields using json_extract().
    Tracks index metadata in _vibedb_indexes table.

    Example:
        >>> manager = IndexManager(collection)
        >>> manager.create_index(["email"], unique=True)
        >>> manager.create_index(["age", "status"])
        >>> manager.list_indexes()
    """

    def __init__(self, collection: "Collection"):
        """
        Initialize index manager for a collection.

        Args:
            collection: Collection to manage indexes for
        """
        self.collection = collection
        self.db = collection.database

    def create_index(
        self,
        fields: List[str],
        unique: bool = False,
        name: str = None
    ) -> str:
        """
        Create an index on specified fields.

        Uses SQLite expression indexes with json_extract() for JSON fields.

        Args:
            fields: List of field names to index (can be nested like "address.city")
            unique: Whether to enforce uniqueness
            name: Optional custom index name (auto-generated if not provided)

        Returns:
            str: Index ID

        Raises:
            VibeIndexError: If index creation fails

        Example:
            >>> # Simple index
            >>> manager.create_index(["email"], unique=True)
            >>>
            >>> # Composite index
            >>> manager.create_index(["age", "status"])
            >>>
            >>> # Nested field index
            >>> manager.create_index(["address.city"])
        """
        # Validate fields
        if not fields or not isinstance(fields, list):
            raise VibeIndexError("Fields must be a non-empty list")

        for field in fields:
            safe_field_name(field)

        # Generate index name if not provided
        if name is None:
            name = self._generate_index_name(fields, unique)

        # Check if index already exists
        existing = self._get_index_by_name(name)
        if existing:
            raise VibeIndexError(f"Index '{name}' already exists", name)

        # Generate index ID
        index_id = self._generate_index_id(name)

        # Create SQLite index
        self._create_sqlite_index(name, fields, unique)

        # Register index in metadata
        self._register_index(index_id, name, fields, unique)

        return index_id

    def drop_index(self, name: str) -> bool:
        """
        Drop an index by name.

        Args:
            name: Index name

        Returns:
            bool: True if index was dropped, False if it didn't exist

        Example:
            >>> manager.drop_index("users_idx_email")
            True
        """
        # Check if index exists
        index = self._get_index_by_name(name)
        if not index:
            return False

        # Drop SQLite index
        self._drop_sqlite_index(name)

        # Remove from metadata
        self._unregister_index(index['id'])

        return True

    def list_indexes(self) -> List[Dict[str, Any]]:
        """
        List all indexes for this collection.

        Returns:
            list[dict]: List of index metadata

        Example:
            >>> indexes = manager.list_indexes()
            >>> for idx in indexes:
            ...     print(f"{idx['name']}: {idx['fields']}")
        """
        sql = """
            SELECT id, name, fields, unique_index, created_at
            FROM _vibedb_indexes
            WHERE collection = ?
            ORDER BY created_at
        """

        rows = self.db.engine.execute(sql, (self.collection.name,), fetch='all')

        indexes = []
        for row in rows:
            import json
            indexes.append({
                'id': row['id'],
                'name': row['name'],
                'fields': json.loads(row['fields']),
                'unique': bool(row['unique_index']),
                'created_at': row['created_at']
            })

        return indexes

    def index_exists(self, name: str) -> bool:
        """
        Check if an index exists.

        Args:
            name: Index name

        Returns:
            bool: True if index exists
        """
        return self._get_index_by_name(name) is not None

    def get_index_info(self, name: str) -> Dict[str, Any]:
        """
        Get information about an index.

        Args:
            name: Index name

        Returns:
            dict: Index metadata or None if not found
        """
        return self._get_index_by_name(name)

    # ==================== Private Methods ====================

    def _generate_index_name(self, fields: List[str], unique: bool) -> str:
        """
        Generate index name from fields.

        Format: {collection}_{u}idx_{field1}_{field2}...
        Example: users_idx_email, posts_uidx_slug

        Args:
            fields: List of field names
            unique: Whether index is unique

        Returns:
            str: Generated index name
        """
        # Replace dots with underscores for nested fields
        field_parts = [f.replace('.', '_') for f in fields]

        # Limit length (SQLite has limits on identifier length)
        field_str = '_'.join(field_parts)
        if len(field_str) > 40:
            # Hash long field combinations
            field_hash = hashlib.md5(field_str.encode()).hexdigest()[:8]
            field_str = f"multi_{field_hash}"

        prefix = "uidx" if unique else "idx"
        return f"{self.collection.name}_{prefix}_{field_str}"

    def _generate_index_id(self, name: str) -> str:
        """
        Generate unique index ID.

        Args:
            name: Index name

        Returns:
            str: Index ID
        """
        # Use collection name + index name + timestamp for uniqueness
        timestamp = current_timestamp()
        id_str = f"{self.collection.name}:{name}:{timestamp}"
        return hashlib.md5(id_str.encode()).hexdigest()[:16]

    def _create_sqlite_index(self, name: str, fields: List[str], unique: bool):
        """
        Create SQLite expression index.

        Args:
            name: Index name
            fields: Field names
            unique: Whether to enforce uniqueness

        Raises:
            VibeIndexError: If creation fails
        """
        try:
            # Sanitize names
            safe_table = self.collection.name.replace('"', '""')
            safe_name = name.replace('"', '""')

            # Build json_extract expressions for each field
            field_exprs = []
            for field in fields:
                json_path = format_json_path(field)
                field_exprs.append(f"json_extract(_doc, '{json_path}')")

            fields_sql = ", ".join(field_exprs)

            # Create index
            unique_keyword = "UNIQUE " if unique else ""
            sql = f'''
                CREATE {unique_keyword}INDEX IF NOT EXISTS "{safe_name}"
                ON "{safe_table}_docs" ({fields_sql})
            '''

            self.db.engine.execute(sql)
            self.db.engine.commit()

        except Exception as e:
            raise VibeIndexError(f"Failed to create index: {e}", name)

    def _drop_sqlite_index(self, name: str):
        """
        Drop SQLite index.

        Args:
            name: Index name

        Raises:
            VibeIndexError: If drop fails
        """
        try:
            safe_name = name.replace('"', '""')
            sql = f'DROP INDEX IF EXISTS "{safe_name}"'

            self.db.engine.execute(sql)
            self.db.engine.commit()

        except Exception as e:
            raise VibeIndexError(f"Failed to drop index: {e}", name)

    def _register_index(self, index_id: str, name: str, fields: List[str], unique: bool):
        """
        Register index in metadata table.

        Args:
            index_id: Index ID
            name: Index name
            fields: Field names
            unique: Whether index is unique
        """
        import json

        sql = """
            INSERT INTO _vibedb_indexes
            (id, collection, name, fields, unique_index, created_at)
            VALUES (?, ?, ?, ?, ?, ?)
        """

        self.db.engine.execute(
            sql,
            (
                index_id,
                self.collection.name,
                name,
                json.dumps(fields),
                1 if unique else 0,
                current_timestamp()
            )
        )
        self.db.engine.commit()

    def _unregister_index(self, index_id: str):
        """
        Remove index from metadata table.

        Args:
            index_id: Index ID
        """
        sql = "DELETE FROM _vibedb_indexes WHERE id = ?"
        self.db.engine.execute(sql, (index_id,))
        self.db.engine.commit()

    def _get_index_by_name(self, name: str) -> Dict[str, Any]:
        """
        Get index metadata by name.

        Args:
            name: Index name

        Returns:
            dict: Index metadata or None
        """
        sql = """
            SELECT id, name, fields, unique_index, created_at
            FROM _vibedb_indexes
            WHERE collection = ? AND name = ?
        """

        result = self.db.engine.execute(
            sql,
            (self.collection.name, name),
            fetch='one'
        )

        if not result:
            return None

        import json
        return {
            'id': result['id'],
            'name': result['name'],
            'fields': json.loads(result['fields']),
            'unique': bool(result['unique_index']),
            'created_at': result['created_at']
        }
