"""SQLite-backed storage for memory chunks."""

from __future__ import annotations

import hashlib
import json
import os
import sqlite3
import time
import uuid
from typing import Any

from thryve.core.memory.types import MemoryChunk
from thryve.utils import get_logger

logger = get_logger("memory.storage")


class SQLiteStorage:
    """Persistent storage for memory chunks backed by SQLite.

    The database is created lazily on first write.
    """

    def __init__(self, db_path: str = "./data/memory.db") -> None:
        self._db_path = db_path
        self._conn: sqlite3.Connection | None = None

    # ------------------------------------------------------------------
    # Connection
    # ------------------------------------------------------------------

    def _ensure_db(self) -> sqlite3.Connection:
        if self._conn is not None:
            return self._conn

        os.makedirs(os.path.dirname(self._db_path) or ".", exist_ok=True)
        self._conn = sqlite3.connect(self._db_path)
        self._conn.row_factory = sqlite3.Row
        self._create_tables()
        return self._conn

    def _create_tables(self) -> None:
        assert self._conn is not None
        self._conn.executescript(
            """
            CREATE TABLE IF NOT EXISTS memory_chunks (
                id          TEXT PRIMARY KEY,
                text        TEXT NOT NULL,
                source      TEXT NOT NULL DEFAULT 'short_term',
                created_at  REAL NOT NULL,
                hash        TEXT NOT NULL,
                metadata    TEXT NOT NULL DEFAULT '{}',
                embedding   BLOB
            );
            CREATE INDEX IF NOT EXISTS idx_source ON memory_chunks(source);
            CREATE INDEX IF NOT EXISTS idx_created_at ON memory_chunks(created_at);
            """
        )
        self._conn.commit()

    # ------------------------------------------------------------------
    # CRUD
    # ------------------------------------------------------------------

    def insert(self, chunk: MemoryChunk) -> None:
        """Insert a memory chunk.  Duplicates (by hash) are silently ignored."""
        conn = self._ensure_db()
        if not chunk.id:
            chunk.id = uuid.uuid4().hex
        if not chunk.hash:
            chunk.hash = hashlib.sha256(chunk.text.encode()).hexdigest()[:16]
        embedding_blob: bytes | None = None
        if chunk.embedding:
            embedding_blob = json.dumps(chunk.embedding).encode()
        conn.execute(
            """
            INSERT OR IGNORE INTO memory_chunks
                (id, text, source, created_at, hash, metadata, embedding)
            VALUES (?, ?, ?, ?, ?, ?, ?)
            """,
            (
                chunk.id,
                chunk.text,
                chunk.source,
                chunk.created_at,
                chunk.hash,
                json.dumps(chunk.metadata),
                embedding_blob,
            ),
        )
        conn.commit()

    def search_by_keyword(
        self,
        query: str,
        top_k: int = 10,
        source: str | None = None,
    ) -> list[MemoryChunk]:
        """Naive keyword search (LIKE %keyword%)."""
        conn = self._ensure_db()
        sql = "SELECT * FROM memory_chunks WHERE text LIKE ?"
        params: list[Any] = [f"%{query}%"]
        if source:
            sql += " AND source = ?"
            params.append(source)
        sql += " ORDER BY created_at DESC LIMIT ?"
        params.append(top_k)
        rows = conn.execute(sql, params).fetchall()
        return [self._row_to_chunk(r) for r in rows]

    def get_all(self, source: str | None = None, limit: int = 100) -> list[MemoryChunk]:
        conn = self._ensure_db()
        sql = "SELECT * FROM memory_chunks"
        params: list[Any] = []
        if source:
            sql += " WHERE source = ?"
            params.append(source)
        sql += " ORDER BY created_at DESC LIMIT ?"
        params.append(limit)
        rows = conn.execute(sql, params).fetchall()
        return [self._row_to_chunk(r) for r in rows]

    def delete_older_than(self, cutoff: float, source: str = "short_term") -> int:
        """Delete chunks older than *cutoff* (epoch seconds). Returns count deleted."""
        conn = self._ensure_db()
        cur = conn.execute(
            "DELETE FROM memory_chunks WHERE source = ? AND created_at < ?",
            (source, cutoff),
        )
        conn.commit()
        return cur.rowcount

    def close(self) -> None:
        if self._conn:
            self._conn.close()
            self._conn = None

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _row_to_chunk(row: sqlite3.Row) -> MemoryChunk:
        emb = None
        if row["embedding"]:
            emb = json.loads(row["embedding"])
        return MemoryChunk(
            id=row["id"],
            text=row["text"],
            source=row["source"],
            created_at=row["created_at"],
            hash=row["hash"],
            metadata=json.loads(row["metadata"]),
            embedding=emb,
        )
