"""
User Memory Store — SQLite-backed storage for User Memory Nodes.

Namespace: openmemo/user/{user_id}
Performance targets: write < 30ms, recall < 50ms, < 500 entries/user
"""

import json
import time
import uuid
import sqlite3
import threading
import logging
from dataclasses import dataclass, field, asdict
from typing import List, Optional, Dict

logger = logging.getLogger("openmemo.user_memory")


# ── Data Model ────────────────────────────────────────────────────────────────

@dataclass
class UserMemoryNode:
    user_id: str
    type: str           # "preference" | "constraint" | "environment" | "behavior"
    key: str            # e.g. "deployment_tool", "language"
    value: str          # e.g. "docker-compose", "Chinese"
    confidence: float = 0.8
    source: str = "agent_inferred"   # "agent_inferred" | "user_explicit"
    usage_count: int = 0
    memory_id: str = field(default_factory=lambda: str(uuid.uuid4()))
    timestamp: float = field(default_factory=time.time)

    def to_dict(self) -> dict:
        return asdict(self)

    @classmethod
    def from_row(cls, row) -> "UserMemoryNode":
        return cls(
            memory_id=row["memory_id"],
            user_id=row["user_id"],
            type=row["type"],
            key=row["key"],
            value=row["value"],
            confidence=row["confidence"],
            source=row["source"],
            usage_count=row["usage_count"],
            timestamp=row["timestamp"],
        )


# ── Store ─────────────────────────────────────────────────────────────────────

class UserMemoryStore:
    """
    SQLite-backed store for User Memory Nodes.

    Usage:
        store = UserMemoryStore("openmemo.db")
        store.put(UserMemoryNode(user_id="alice", type="preference",
                                  key="backend", value="FastAPI"))
        nodes = store.get_by_user("alice")
    """

    def __init__(self, db_path: str = "openmemo.db"):
        self.db_path = db_path
        self._local = threading.local()
        self._init_table()

    @property
    def conn(self) -> sqlite3.Connection:
        if not hasattr(self._local, "conn") or self._local.conn is None:
            c = sqlite3.connect(self.db_path, check_same_thread=False)
            c.row_factory = sqlite3.Row
            c.execute("PRAGMA journal_mode=WAL")
            c.execute("PRAGMA synchronous=NORMAL")
            self._local.conn = c
        return self._local.conn

    def _init_table(self):
        self.conn.execute("""
            CREATE TABLE IF NOT EXISTS user_memory (
                memory_id   TEXT PRIMARY KEY,
                user_id     TEXT NOT NULL,
                type        TEXT NOT NULL,
                key         TEXT NOT NULL,
                value       TEXT NOT NULL,
                confidence  REAL DEFAULT 0.8,
                source      TEXT DEFAULT 'agent_inferred',
                usage_count INTEGER DEFAULT 0,
                timestamp   REAL NOT NULL
            )
        """)
        self.conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_um_user ON user_memory(user_id)"
        )
        self.conn.execute(
            "CREATE INDEX IF NOT EXISTS idx_um_type ON user_memory(user_id, type)"
        )
        self.conn.commit()

    # ── Write ──────────────────────────────────────────────────────────────────

    def put(self, node: UserMemoryNode) -> str:
        """Insert or update a user memory node. Returns memory_id."""
        # Upsert: if same user+key exists, update value & confidence
        existing = self._find_by_key(node.user_id, node.key)
        if existing:
            self.conn.execute(
                """UPDATE user_memory
                   SET value=?, confidence=?, source=?, timestamp=?
                   WHERE memory_id=?""",
                (node.value, node.confidence, node.source,
                 time.time(), existing.memory_id)
            )
            self.conn.commit()
            logger.debug("user_memory upsert key=%s user=%s", node.key, node.user_id)
            return existing.memory_id
        else:
            self.conn.execute(
                """INSERT INTO user_memory
                   (memory_id, user_id, type, key, value,
                    confidence, source, usage_count, timestamp)
                   VALUES (?,?,?,?,?,?,?,?,?)""",
                (node.memory_id, node.user_id, node.type, node.key,
                 node.value, node.confidence, node.source,
                 node.usage_count, node.timestamp)
            )
            self.conn.commit()
            logger.debug("user_memory insert key=%s user=%s", node.key, node.user_id)
            return node.memory_id

    def _find_by_key(self, user_id: str, key: str) -> Optional[UserMemoryNode]:
        row = self.conn.execute(
            "SELECT * FROM user_memory WHERE user_id=? AND key=? LIMIT 1",
            (user_id, key)
        ).fetchone()
        return UserMemoryNode.from_row(row) if row else None

    def increment_usage(self, memory_id: str):
        self.conn.execute(
            "UPDATE user_memory SET usage_count = usage_count + 1 WHERE memory_id=?",
            (memory_id,)
        )
        self.conn.commit()

    # ── Read ───────────────────────────────────────────────────────────────────

    def get_by_user(self, user_id: str,
                    mem_type: str = "",
                    limit: int = 500) -> List[UserMemoryNode]:
        if mem_type:
            rows = self.conn.execute(
                """SELECT * FROM user_memory
                   WHERE user_id=? AND type=?
                   ORDER BY timestamp DESC LIMIT ?""",
                (user_id, mem_type, limit)
            ).fetchall()
        else:
            rows = self.conn.execute(
                """SELECT * FROM user_memory
                   WHERE user_id=?
                   ORDER BY timestamp DESC LIMIT ?""",
                (user_id, limit)
            ).fetchall()
        return [UserMemoryNode.from_row(r) for r in rows]

    def get(self, memory_id: str) -> Optional[UserMemoryNode]:
        row = self.conn.execute(
            "SELECT * FROM user_memory WHERE memory_id=?",
            (memory_id,)
        ).fetchone()
        return UserMemoryNode.from_row(row) if row else None

    def delete(self, memory_id: str) -> bool:
        cur = self.conn.execute(
            "DELETE FROM user_memory WHERE memory_id=?", (memory_id,)
        )
        self.conn.commit()
        return cur.rowcount > 0

    def delete_by_user(self, user_id: str) -> int:
        cur = self.conn.execute(
            "DELETE FROM user_memory WHERE user_id=?", (user_id,)
        )
        self.conn.commit()
        return cur.rowcount

    def count(self, user_id: str) -> int:
        row = self.conn.execute(
            "SELECT COUNT(*) as n FROM user_memory WHERE user_id=?",
            (user_id,)
        ).fetchone()
        return row["n"] if row else 0

    def close(self):
        if hasattr(self._local, "conn") and self._local.conn:
            self._local.conn.close()
            self._local.conn = None
