import sqlite3
from pathlib import Path


class Memory:
    """SQLite FTS5-backed memory index."""

    def __init__(self, db_path: Path):
        """Open or create SQLite database with FTS5 virtual table."""
        self._db_path = Path(db_path)
        self._db_path.parent.mkdir(parents=True, exist_ok=True)

        self._conn = sqlite3.connect(str(self._db_path), check_same_thread=False)
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("CREATE VIRTUAL TABLE IF NOT EXISTS memory USING fts5(key, content)")
        self._conn.commit()

    def index(self, key: str, content: str) -> None:
        """Index content under a key (upsert)."""
        with self._conn:
            self._conn.execute("DELETE FROM memory WHERE key = ?", (key,))
            self._conn.execute(
                "INSERT INTO memory (key, content) VALUES (?, ?)",
                (key, content),
            )

    def store(self, key: str, content: str) -> None:
        """Store content under a key (alias for index)."""
        self.index(key, content)

    def get(self, key: str) -> str | None:
        """Retrieve content by exact key, or None if not found."""
        row = self._conn.execute("SELECT content FROM memory WHERE key = ?", (key,)).fetchone()
        if row is None:
            return None
        return str(row["content"])

    def search(self, query: str, limit: int = 10) -> list[dict]:
        """Search indexed content using FTS5 MATCH syntax."""
        if not query or not query.strip():
            return []

        if limit <= 0:
            return []

        # Quote the query to prevent FTS5 operator injection (AND, OR, NOT, *)
        safe_query = '"' + query.replace('"', '""') + '"'

        try:
            rows = self._conn.execute(
                """
                SELECT
                    key,
                    snippet(memory, 1, '<b>', '</b>', '...', 32) AS snippet,
                    rank
                FROM memory
                WHERE memory MATCH ?
                ORDER BY rank
                LIMIT ?
                """,
                (safe_query, limit),
            ).fetchall()
        except sqlite3.OperationalError:
            return []

        return [
            {
                "key": str(row["key"]),
                "snippet": str(row["snippet"]),
                "rank": float(row["rank"]),
            }
            for row in rows
        ]

    def delete(self, key: str) -> None:
        """Remove a key from the index."""
        with self._conn:
            self._conn.execute("DELETE FROM memory WHERE key = ?", (key,))

    def delete_prefix(self, prefix: str) -> int:
        """Delete all keys starting with prefix. Returns count of deleted rows."""
        if not prefix:
            return 0

        with self._conn:
            escaped = prefix.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
            cursor = self._conn.execute(
                "DELETE FROM memory WHERE key LIKE ? || '%' ESCAPE '\\'",
                (escaped,),
            )
            return cursor.rowcount

    def count(self) -> int:
        """Return total number of indexed entries."""
        row = self._conn.execute("SELECT COUNT(*) AS total FROM memory").fetchone()
        if row is None:
            return 0
        return int(row["total"])

    def close(self) -> None:
        """Close the database connection."""
        self._conn.close()
