"""SQLite-backed memory backend."""
from __future__ import annotations

from datetime import datetime
import json
from pathlib import Path
import re
import sqlite3

from logos_adk.errors import MemoryBackendError
from logos_adk.memory import MemoryBackend, MemoryEntry
from logos_adk.persistence import get_sqlite_schema_version, set_sqlite_schema_version


MEMORY_SCHEMA_KIND = "memory_backend"
MEMORY_SCHEMA_VERSION = 1


class SQLiteBackend(MemoryBackend):
    """Persistent memory backend using SQLite."""

    def __init__(self, path: str) -> None:
        self.path = str(path)

    async def store(self, entry: MemoryEntry) -> None:
        self._store_sync(entry)

    async def search(
        self,
        query: str,
        limit: int = 10,
        session_id: str | None = None,
    ) -> list[MemoryEntry]:
        return self._search_sync(query, limit, session_id)

    async def delete(self, entry_id: str) -> None:
        self._delete_sync(entry_id)

    def _connect(self) -> sqlite3.Connection:
        try:
            db_path = Path(self.path)
            if db_path.parent and not db_path.parent.exists():
                db_path.parent.mkdir(parents=True, exist_ok=True)
            connection = sqlite3.connect(self.path)
            connection.row_factory = sqlite3.Row
            return connection
        except sqlite3.Error as exc:
            raise MemoryBackendError(f"Failed to open SQLite memory backend: {exc}") from exc

    def _ensure_schema(self, connection: sqlite3.Connection) -> None:
        connection.execute(
            """
            CREATE TABLE IF NOT EXISTS memories (
                id TEXT PRIMARY KEY,
                content TEXT NOT NULL,
                embedding TEXT,
                metadata TEXT NOT NULL,
                created_at TEXT NOT NULL,
                session_id TEXT
            )
            """
        )
        recorded_version = get_sqlite_schema_version(connection, MEMORY_SCHEMA_KIND)
        if recorded_version != MEMORY_SCHEMA_VERSION:
            set_sqlite_schema_version(connection, MEMORY_SCHEMA_KIND, MEMORY_SCHEMA_VERSION)
        connection.commit()

    def _store_sync(self, entry: MemoryEntry) -> None:
        try:
            with self._connect() as connection:
                self._ensure_schema(connection)
                connection.execute(
                    """
                    INSERT OR REPLACE INTO memories
                    (id, content, embedding, metadata, created_at, session_id)
                    VALUES (?, ?, ?, ?, ?, ?)
                    """,
                    (
                        entry.id,
                        entry.content,
                        json.dumps(entry.embedding) if entry.embedding is not None else None,
                        json.dumps(entry.metadata),
                        entry.created_at.isoformat(),
                        entry.session_id,
                    ),
                )
                connection.commit()
        except sqlite3.Error as exc:
            raise MemoryBackendError(f"Failed to store memory entry: {exc}") from exc

    def _search_sync(
        self,
        query: str,
        limit: int,
        session_id: str | None,
    ) -> list[MemoryEntry]:
        normalized_query = query.strip().lower()
        keywords = [
            token
            for token in re.findall(r"[a-zA-Z0-9_]+", normalized_query)
            if len(token) >= 4
        ]

        try:
            with self._connect() as connection:
                self._ensure_schema(connection)
                clauses: list[str] = []
                params: list[object] = []

                if normalized_query:
                    content_matchers = ["LOWER(content) LIKE LOWER(?)"]
                    content_params: list[object] = [f"%{normalized_query}%"]
                    if keywords:
                        content_matchers.extend("LOWER(content) LIKE LOWER(?)" for _ in keywords)
                        content_params.extend(f"%{keyword}%" for keyword in keywords)

                    clauses.append("(" + " OR ".join(content_matchers) + ")")
                    params.extend(content_params)

                if session_id is None:
                    clauses.append("session_id IS NULL")
                else:
                    clauses.append("session_id = ?")
                    params.append(session_id)

                params.append(limit)
                rows = connection.execute(
                    f"""
                    SELECT id, content, embedding, metadata, created_at, session_id
                    FROM memories
                    WHERE {' AND '.join(clauses)}
                    ORDER BY created_at DESC
                    LIMIT ?
                    """,
                    params,
                ).fetchall()
        except sqlite3.Error as exc:
            raise MemoryBackendError(f"Failed to search memory entries: {exc}") from exc

        return [self._row_to_entry(row) for row in rows]

    def _delete_sync(self, entry_id: str) -> None:
        try:
            with self._connect() as connection:
                self._ensure_schema(connection)
                connection.execute("DELETE FROM memories WHERE id = ?", (entry_id,))
                connection.commit()
        except sqlite3.Error as exc:
            raise MemoryBackendError(f"Failed to delete memory entry: {exc}") from exc

    def _row_to_entry(self, row: sqlite3.Row) -> MemoryEntry:
        embedding = json.loads(row["embedding"]) if row["embedding"] else None
        metadata = json.loads(row["metadata"]) if row["metadata"] else {}
        created_at = datetime.fromisoformat(row["created_at"])
        return MemoryEntry(
            id=row["id"],
            content=row["content"],
            embedding=embedding,
            metadata=metadata,
            created_at=created_at,
            session_id=row["session_id"],
        )
