"""Versioned scoped memory. Scope is trusted input here, not authentication."""
import json
import sqlite3
from contextlib import contextmanager


def connect(path):
    db = sqlite3.connect(path, isolation_level=None, timeout=5)
    db.row_factory = sqlite3.Row
    db.execute('PRAGMA foreign_keys=ON')
    db.executescript('''
      CREATE TABLE IF NOT EXISTS memory_revisions(scope TEXT PRIMARY KEY, rev INTEGER NOT NULL);
      CREATE TABLE IF NOT EXISTS memories(
        scope TEXT NOT NULL, key TEXT NOT NULL, version INTEGER NOT NULL,
        value TEXT NOT NULL, source TEXT NOT NULL, confirmed INTEGER NOT NULL,
        expires REAL NOT NULL, active INTEGER NOT NULL,
        PRIMARY KEY(scope,key,version));
      CREATE UNIQUE INDEX IF NOT EXISTS one_active_memory
        ON memories(scope,key) WHERE active=1;
    ''')
    return db


@contextmanager
def transaction(db):
    db.execute('BEGIN IMMEDIATE')
    try:
        yield
        db.commit()
    except BaseException:
        db.rollback()
        raise


def scope_key(tenant, user, project):
    return json.dumps([tenant, user, project], ensure_ascii=False, separators=(',', ':'))


def revision(db, scope):
    row = db.execute('SELECT rev FROM memory_revisions WHERE scope=?', (scope,)).fetchone()
    return row['rev'] if row else 0


def bump(db, scope):
    db.execute('INSERT INTO memory_revisions VALUES (?,1) '
               'ON CONFLICT(scope) DO UPDATE SET rev=rev+1', (scope,))


def put(db, scope, key, value, source, expires, confirmed=True):
    if not source or not key:
        raise ValueError('source and key required')
    with transaction(db):
        version = db.execute('SELECT COALESCE(MAX(version),0)+1 FROM memories '
                             'WHERE scope=? AND key=?', (scope, key)).fetchone()[0]
        db.execute('UPDATE memories SET active=0 WHERE scope=? AND key=?', (scope, key))
        db.execute('INSERT INTO memories VALUES (?,?,?,?,?,?,?,1)',
                   (scope, key, version, value, source, int(confirmed), expires))
        bump(db, scope)
    return version


def forget(db, scope, key):
    # Logical revocation plus payload erasure in this table; not backup erasure.
    with transaction(db):
        db.execute("UPDATE memories SET active=0,value='[deleted]' WHERE scope=? AND key=?",
                   (scope, key))
        bump(db, scope)


def current(db, scope, now):
    return [dict(r) for r in db.execute('SELECT key,version,value,source,expires FROM memories '
             'WHERE scope=? AND active=1 AND confirmed=1 AND expires>? ORDER BY key', (scope, now))]
