"""SQLite — the one local store for accounts, tokens (and later jobs, cache, usage).

Dependency-light by design (stdlib sqlite3). WAL mode so the API process and the
worker daemon can read/write concurrently without locking each other out. The DB
file (config.DB_PATH) is the ONE thing worth backing up — everything else
(artifact cache) is rebuildable. See SRE.md.
"""
import sqlite3
from contextlib import contextmanager

import config

SCHEMA = """
CREATE TABLE IF NOT EXISTS accounts (
    id      INTEGER PRIMARY KEY,
    name    TEXT UNIQUE NOT NULL,
    created REAL NOT NULL,
    notes   TEXT DEFAULT ''
);
CREATE TABLE IF NOT EXISTS tokens (
    id         INTEGER PRIMARY KEY,
    token_hash TEXT UNIQUE NOT NULL,   -- sha256(plaintext); plaintext never stored
    prefix     TEXT NOT NULL,          -- first chars of plaintext, for display only
    account_id INTEGER NOT NULL REFERENCES accounts(id),
    scopes     TEXT NOT NULL DEFAULT '',  -- space-separated; '*' = all engines
    quota_json TEXT,                    -- NULL = unlimited (used by #24)
    created    REAL NOT NULL,
    expires_at REAL,                    -- NULL = never
    revoked    INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_tokens_hash ON tokens(token_hash);

-- content-addressed result cache (#26): the margin lever. A row per
-- (content_id, kind, params) — JSON results inline in result_json, big binary
-- artifacts referenced by result_ref (a freebox path, filled by #27).
CREATE TABLE IF NOT EXISTS cache (
    id          INTEGER PRIMARY KEY,
    content_id  TEXT NOT NULL,          -- sha256 of decoded audio (canon)
    kind        TEXT NOT NULL,          -- emotion | features | samples | stems | loops | source
    params_hash TEXT NOT NULL DEFAULT '',  -- sha256 of engine params (model, bars…)
    result_json TEXT,                   -- inline JSON result (cheap analyses)
    result_ref  TEXT,                   -- path to a big artifact (stems/loops)
    bytes       INTEGER NOT NULL DEFAULT 0,
    created     REAL NOT NULL,
    last_access REAL NOT NULL,
    hits        INTEGER NOT NULL DEFAULT 0,
    UNIQUE(content_id, kind, params_hash)
);
CREATE INDEX IF NOT EXISTS idx_cache_lru ON cache(kind, last_access);

-- fast alias → content_id (e.g. a youtube id), so a repeat URL can skip the
-- download when we've already seen that source (#29). Canon stays content_id.
CREATE TABLE IF NOT EXISTS aliases (
    alias      TEXT PRIMARY KEY,
    content_id TEXT NOT NULL,
    created    REAL NOT NULL
);

-- async jobs (#25): heavy work (sources/separate/loops) runs out-of-band so the
-- API stays responsive. The API enqueues (pending) + returns a job_id; a single
-- worker daemon (worker.py) atomically claims FIFO+priority, runs the registered
-- handler, writes progress + terminal state. Identity is the gateway tenant
-- (X-Tenant) stored as `account` text — central auth owns the accounts table now.
-- status: pending | running | done | error | cancelled.
CREATE TABLE IF NOT EXISTS jobs (
    id          TEXT PRIMARY KEY,          -- j_<hex>
    account     TEXT NOT NULL,             -- gateway tenant (X-Tenant); '' for dev
    type        TEXT NOT NULL,             -- separate | sources | loops | …
    params_json TEXT NOT NULL DEFAULT '{}',
    status      TEXT NOT NULL DEFAULT 'pending',
    priority    INTEGER NOT NULL DEFAULT 0,
    progress    REAL NOT NULL DEFAULT 0,
    result_json TEXT,                      -- inline JSON result
    result_ref  TEXT,                      -- artifact base path (X-Accel-Redirect)
    error       TEXT,
    attempts    INTEGER NOT NULL DEFAULT 0,
    webhook_url TEXT,
    created     REAL NOT NULL,
    started     REAL,
    finished    REAL
);
-- the worker's claim query orders by this; keeps the hot 'pending' scan tight.
CREATE INDEX IF NOT EXISTS idx_jobs_claim   ON jobs(status, priority DESC, created);
CREATE INDEX IF NOT EXISTS idx_jobs_account ON jobs(account, created);
"""


def connect() -> sqlite3.Connection:
    config.DB_PATH.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(config.DB_PATH, timeout=10)
    conn.row_factory = sqlite3.Row
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA foreign_keys=ON")
    return conn


def init() -> None:
    with connect() as conn:
        conn.executescript(SCHEMA)


@contextmanager
def cursor():
    """Auto-init + commit/rollback context."""
    conn = connect()
    try:
        conn.executescript(SCHEMA)   # idempotent; cheap (IF NOT EXISTS)
        yield conn
        conn.commit()
    except Exception:
        conn.rollback()
        raise
    finally:
        conn.close()
