"""person_db.py -- Persistent SQLite person gallery for saltybot enrollment."""

import sqlite3
import json
import time
import numpy as np
import threading
from pathlib import Path


class PersonDB:
    """Thread-safe SQLite-backed person gallery.

    Schema:
      persons(id INTEGER PRIMARY KEY, name TEXT, enrolled_at REAL,
              sample_count INTEGER, embedding BLOB, metadata TEXT)
      voice_samples(id INTEGER PRIMARY KEY, person_id INTEGER REFERENCES persons,
                    recorded_at REAL, sample_path TEXT)
    """

    def __init__(self, db_path: str):
        self._db_path = db_path
        self._lock = threading.Lock()
        Path(db_path).parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    def _init_db(self):
        with self._connect() as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS persons (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT NOT NULL,
                    enrolled_at REAL NOT NULL,
                    sample_count INTEGER DEFAULT 1,
                    embedding BLOB,
                    metadata TEXT DEFAULT '{}'
                )
            """)
            conn.execute("""
                CREATE TABLE IF NOT EXISTS voice_samples (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    person_id INTEGER REFERENCES persons(id),
                    recorded_at REAL NOT NULL,
                    sample_path TEXT NOT NULL
                )
            """)

    def _connect(self):
        return sqlite3.connect(self._db_path)

    def add_person(self, name: str, embedding: np.ndarray, sample_count: int = 1,
                   metadata: dict = None) -> int:
        """Add a new person. Returns new person_id."""
        emb_blob = embedding.astype(np.float32).tobytes() if embedding is not None else None
        now = time.time()
        with self._lock:
            with self._connect() as conn:
                cur = conn.execute(
                    "INSERT INTO persons (name, enrolled_at, sample_count, embedding, metadata) "
                    "VALUES (?, ?, ?, ?, ?)",
                    (name, now, sample_count, emb_blob, json.dumps(metadata or {}))
                )
                return cur.lastrowid

    def update_embedding(self, person_id: int, embedding: np.ndarray,
                         sample_count: int) -> bool:
        emb_blob = embedding.astype(np.float32).tobytes()
        with self._lock:
            with self._connect() as conn:
                conn.execute(
                    "UPDATE persons SET embedding=?, sample_count=? WHERE id=?",
                    (emb_blob, sample_count, person_id)
                )
                return conn.total_changes > 0

    def update_name(self, person_id: int, new_name: str) -> bool:
        with self._lock:
            with self._connect() as conn:
                conn.execute(
                    "UPDATE persons SET name=? WHERE id=?",
                    (new_name, person_id)
                )
                return conn.total_changes > 0

    def delete_person(self, person_id: int) -> bool:
        with self._lock:
            with self._connect() as conn:
                conn.execute(
                    "DELETE FROM voice_samples WHERE person_id=?",
                    (person_id,)
                )
                conn.execute(
                    "DELETE FROM persons WHERE id=?",
                    (person_id,)
                )
                return conn.total_changes > 0

    def get_all(self) -> list:
        """Returns list of dicts with id, name, enrolled_at, sample_count, embedding."""
        with self._lock:
            with self._connect() as conn:
                rows = conn.execute(
                    "SELECT id, name, enrolled_at, sample_count, embedding FROM persons"
                ).fetchall()
        result = []
        for row in rows:
            emb = np.frombuffer(row[4], dtype=np.float32).copy() if row[4] else None
            result.append({
                'id': row[0], 'name': row[1], 'enrolled_at': row[2],
                'sample_count': row[3], 'embedding': emb
            })
        return result

    def get_person(self, person_id: int) -> dict | None:
        with self._lock:
            with self._connect() as conn:
                row = conn.execute(
                    "SELECT id, name, enrolled_at, sample_count, embedding "
                    "FROM persons WHERE id=?",
                    (person_id,)
                ).fetchone()
        if row is None:
            return None
        emb = np.frombuffer(row[4], dtype=np.float32).copy() if row[4] else None
        return {
            'id': row[0], 'name': row[1], 'enrolled_at': row[2],
            'sample_count': row[3], 'embedding': emb
        }

    def add_voice_sample(self, person_id: int, sample_path: str) -> int:
        with self._lock:
            with self._connect() as conn:
                cur = conn.execute(
                    "INSERT INTO voice_samples (person_id, recorded_at, sample_path) "
                    "VALUES (?, ?, ?)",
                    (person_id, time.time(), sample_path)
                )
                return cur.lastrowid
