"""Local teaching example: one inventory update per stable event ID."""
import hashlib
import json
import sqlite3
from contextlib import closing


def initialize(database):
    with closing(sqlite3.connect(database)) as connection:
        connection.executescript("""
            CREATE TABLE IF NOT EXISTS events (
                event_id TEXT PRIMARY KEY,
                payload_hash TEXT NOT NULL
            );
            CREATE TABLE IF NOT EXISTS inventory (
                sku TEXT PRIMARY KEY,
                units INTEGER NOT NULL
            );
        """)


def apply_event(database, event_id, sku, units, *, fail_at=None):
    if not isinstance(event_id, str) or not event_id:
        raise ValueError("event_id must be a nonempty string")
    if not isinstance(sku, str) or not sku:
        raise ValueError("sku must be a nonempty string")
    if type(units) is not int or not 0 <= units <= 1_000_000:
        raise ValueError("units must be an integer from 0 through 1000000")
    digest = hashlib.sha256(json.dumps([sku, units], ensure_ascii=False,
                                      separators=(",", ":")).encode()).hexdigest()
    connection = sqlite3.connect(database, timeout=5)
    try:
        connection.execute("BEGIN IMMEDIATE")
        prior = connection.execute(
            "SELECT payload_hash FROM events WHERE event_id=?", (event_id,)
        ).fetchone()
        if prior:
            if prior[0] != digest:
                raise ValueError("Event ID reused with different data")
            connection.rollback()
            return "already_applied"
        connection.execute("INSERT INTO events VALUES (?, ?)", (event_id, digest))
        connection.execute("""
            INSERT INTO inventory (sku, units) VALUES (?, ?)
            ON CONFLICT(sku) DO UPDATE SET units=inventory.units+excluded.units
        """, (sku, units))
        if fail_at == "before_commit":
            raise RuntimeError("Simulated failure before commit")
        connection.commit()
        if fail_at == "after_commit":
            raise RuntimeError("Simulated lost acknowledgement after commit")
        return "applied"
    except BaseException:
        connection.rollback()
        raise
    finally:
        connection.close()
