"""Long-term memory storage for cross-session facts."""

from __future__ import annotations

import json
import uuid
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any

from database import Database
from logging_config import get_logger

log = get_logger(__name__)


@dataclass
class MemoryEntry:
    """A single fact stored in long-term memory."""

    id: str
    content: str
    created_at: str
    source_session: str
    tags: list[str] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        """Serialize to a plain dict."""
        return {
            "id": self.id,
            "content": self.content,
            "created_at": self.created_at,
            "source_session": self.source_session,
            "tags": self.tags,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> MemoryEntry:
        """Deserialize from a plain dict."""
        return cls(
            id=data["id"],
            content=data["content"],
            created_at=data["created_at"],
            source_session=data["source_session"],
            tags=data.get("tags", []),
        )


class MemoryStore:
    """SQLite-backed long-term memory persistence."""

    def __init__(self, db: Database) -> None:
        self._db = db
        self._log = log.bind(component="memory_store")

    @staticmethod
    def _row_to_entry(row: Any) -> MemoryEntry:
        return MemoryEntry(
            id=row["id"],
            content=row["content"],
            created_at=row["created_at"],
            source_session=row["source_session"],
            tags=json.loads(row["tags"]),
        )

    def add(
        self,
        content: str,
        session_id: str,
        tags: list[str] | None = None,
    ) -> MemoryEntry:
        """Add a new memory entry."""
        entry = MemoryEntry(
            id=uuid.uuid4().hex[:8],
            content=content,
            created_at=datetime.now(timezone.utc).isoformat(),
            source_session=session_id,
            tags=tags or [],
        )
        self._db.conn.execute(
            """INSERT INTO memories (id, content, created_at, source_session, tags)
               VALUES (?, ?, ?, ?, ?)""",
            (
                entry.id,
                entry.content,
                entry.created_at,
                entry.source_session,
                json.dumps(entry.tags),
            ),
        )
        self._db.conn.commit()
        self._log.info("memory_added", memory_id=entry.id, content_preview=content[:50])
        return entry

    def remove(self, memory_id: str) -> bool:
        """Remove a memory entry. Returns True if removed, False if not found."""
        cursor = self._db.conn.execute(
            "DELETE FROM memories WHERE id = ?", (memory_id,)
        )
        self._db.conn.commit()
        if cursor.rowcount > 0:
            self._log.info("memory_removed", memory_id=memory_id)
            return True
        return False

    def list_all(self) -> list[MemoryEntry]:
        """List all memory entries, sorted by creation date (newest first)."""
        rows = self._db.conn.execute(
            "SELECT * FROM memories ORDER BY created_at DESC"
        ).fetchall()
        return [self._row_to_entry(row) for row in rows]

    def search(self, query: str) -> list[MemoryEntry]:
        """Search memories by keyword (case-insensitive substring match).

        Matches against both content and tags.
        """
        pattern = f"%{query}%"
        rows = self._db.conn.execute(
            """SELECT * FROM memories
               WHERE content LIKE ? COLLATE NOCASE
                  OR tags LIKE ? COLLATE NOCASE
               ORDER BY created_at DESC""",
            (pattern, pattern),
        ).fetchall()
        return [self._row_to_entry(row) for row in rows]

    def get_context_string(self, limit: int = 10) -> str:
        """Generate a context string for injection into the system prompt.

        Returns an empty string if there are no memories.
        """
        memories = self.list_all()[:limit]
        if not memories:
            return ""

        lines = ["## Things you remember about this user"]
        for entry in memories:
            lines.append(f"- {entry.content}")

        return "\n".join(lines)
