#!/usr/bin/env python3
"""Original LockedIn Labs retrieval-limit lab. Python 3.11+, SQLite FTS5.
Eight independent in-memory cases; synthetic rows only. No model, network,
external file, authentication service or production authorization adapter.
"""
import argparse
import json
import platform
import sqlite3

CASES = ("post-filter", "overfetch", "pre-limit", "revoked", "corrected", "wrong-workspace", "no-grant", "no-match")
GLOBAL_SQL = """SELECT rowid AS indexed_row, tenant, doc_id, revision,
 bm25(search) AS score FROM search WHERE search MATCH ?
 ORDER BY score, rowid LIMIT ?"""
ELIGIBLE_SQL = """SELECT search.rowid AS indexed_row, search.tenant,
 search.doc_id, search.revision, bm25(search) AS score
 FROM search JOIN sources AS s
 ON s.tenant=search.tenant AND s.id=search.doc_id
 WHERE search MATCH ? AND s.tenant=? AND s.state='active'
 AND s.revision=search.revision AND EXISTS
 (SELECT 1 FROM grants AS g WHERE g.tenant=s.tenant AND g.doc_id=s.id
 AND g.subject=? AND g.action='read')
 ORDER BY score, search.rowid LIMIT ?"""
BODY_SQL = """SELECT s.tenant,s.id AS doc_id,s.revision,s.body FROM sources AS s
 WHERE s.tenant=? AND s.id=? AND s.state='active' AND s.revision=?
 AND EXISTS (SELECT 1 FROM grants AS g WHERE g.tenant=s.tenant
 AND g.doc_id=s.id AND g.subject=? AND g.action='read')"""


def database():
    db = sqlite3.connect(":memory:")
    db.row_factory = sqlite3.Row
    db.execute("CREATE TABLE sources(tenant TEXT,id TEXT,revision INTEGER,state TEXT,body TEXT,PRIMARY KEY(tenant,id))")
    db.execute("CREATE TABLE grants(tenant TEXT,doc_id TEXT,subject TEXT,action TEXT,PRIMARY KEY(tenant,doc_id,subject,action))")
    try:
        db.execute("CREATE VIRTUAL TABLE search USING fts5(tenant UNINDEXED,doc_id UNINDEXED,revision UNINDEXED,body)")
    except sqlite3.OperationalError as error:
        db.close()
        raise RuntimeError("SQLite with built-in FTS5 is required; no extension is downloaded.") from error
    fixtures = [("South", "S-OTHER", 9), ("North", "N-RESTRICTED", 7),
                ("North", "N-A", 5), ("North", "N-B", 3), ("North", "N-C", 1)]
    with db:
        for tenant, doc, occurrences in fixtures:
            # Equal 20-token lengths; actual FTS5 BM25 determines the order.
            body = " ".join(["handoff"] * occurrences + ["reference"] * (20 - occurrences))
            db.execute("INSERT INTO sources VALUES(?,?,1,'active',?)", (tenant, doc, body))
            db.execute("INSERT INTO search VALUES(?,?,1,?)", (tenant, doc, body))
        for tenant, doc in [("North", "N-A"), ("North", "N-B"), ("North", "N-C"), ("South", "S-OTHER")]:
            db.execute("INSERT INTO grants VALUES(?,?,'U-7','read')", (tenant, doc))
    return db


def select(db, term, limit, principal=None):
    phrase = '"' + term.replace('"', '""') + '"'
    args = (phrase, principal["tenant"], principal["subject"], limit) if principal else (phrase, limit)
    return [dict(row) for row in db.execute(ELIGIBLE_SQL if principal else GLOBAL_SQL, args)]


def assemble(db, candidates, principal):
    context, decisions = [], []
    for candidate in candidates:
        tenant, doc, revision = candidate["tenant"], candidate["doc_id"], candidate["revision"]
        metadata = db.execute("SELECT revision,state FROM sources WHERE tenant=? AND id=?", (tenant, doc)).fetchone()
        grant = db.execute("SELECT 1 FROM grants WHERE tenant=? AND doc_id=? AND subject=? AND action='read'",
                           (tenant, doc, principal["subject"])).fetchone()
        if tenant != principal["tenant"]:
            reason = "wrong_workspace"
        elif metadata is None:
            reason = "source_missing"
        elif not grant:
            reason = "permission_missing"
        elif metadata["state"] != "active":
            reason = "source_inactive"
        elif metadata["revision"] != revision:
            reason = "source_revision_changed"
        else:
            # Repeat all predicates on the actual body read. This is still only
            # one local sequential fixture, not a concurrent revocation protocol.
            row = db.execute(BODY_SQL, (principal["tenant"], doc, revision, principal["subject"])).fetchone()
            if row is None:
                reason = "body_read_not_admitted"
            else:
                context.append(dict(row))
                reason = "admitted_current_source"
        decisions.append({"tenant": tenant, "doc_id": doc, "indexed_revision": revision,
                          "current_revision": metadata["revision"] if metadata else None, "reason": reason})
    return {"state": "context_ready" if context else "no_admitted_context", "context": context, "decisions": decisions}


def unchecked_index_copy(db, candidates):
    """Deliberately broken comparison: stale indexed text with no current gate."""
    return [dict(db.execute("SELECT tenant,doc_id,revision,body FROM search WHERE rowid=?",
                            (candidate["indexed_row"],)).fetchone()) for candidate in candidates]


def run_case(name, limit=2):
    if name not in CASES or type(limit) is not int or limit not in (1, 2, 3):
        raise ValueError("unsupported_case_or_limit")
    db = database()
    try:
        principal = {"subject": "U-8" if name == "no-grant" else "U-7", "tenant": "North"}
        requested_workspace = "South" if name == "wrong-workspace" else "North"
        term = "volcano" if name == "no-match" else "handoff"
        eligible_before = select(db, term, 100, principal)
        depth = limit + 1 if name == "overfetch" else limit
        mode = "global_then_filter" if name in ("post-filter", "overfetch") else "eligible_before_limit"
        candidates = select(db, term, depth, None if mode == "global_then_filter" else principal)
        change = "none"
        with db:
            if name == "revoked":
                db.execute("DELETE FROM grants WHERE tenant='North' AND doc_id='N-A' AND subject='U-7' AND action='read'")
                change = "North/N-A read grant removed after selection"
            elif name == "corrected":
                db.execute("UPDATE sources SET revision=2,body='handoff revised source evidence' WHERE tenant='North' AND id='N-A'")
                change = "North/N-A source changed to revision 2; index still revision 1"
        result = assemble(db, candidates, principal)
        unchecked = unchecked_index_copy(db, candidates) if name in ("revoked", "corrected") else []
        return {"case": name, "requested_limit": limit, "candidate_depth": depth,
                "trusted_fixture_context": principal, "requested_workspace": requested_workspace,
                "query": term, "selection_mode": mode, "candidate_sql": GLOBAL_SQL if mode == "global_then_filter" else ELIGIBLE_SQL,
                "all_matching_eligible_before": eligible_before, "candidates": candidates, "change": change,
                **result, "deliberately_unchecked_index_context": unchecked,
                "fresh_selection_after_change": select(db, term, limit, principal) if change != "none" else None,
                "context_was_not_automatically_refilled": True}
    finally:
        db.close()


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--case", choices=CASES)
    parser.add_argument("--limit", type=int, choices=(1, 2, 3), default=2)
    args = parser.parse_args()
    reports = [run_case(name, args.limit) for name in ([args.case] if args.case else CASES)]
    print(json.dumps({"schema": "lockedin.retrieval-limits/v1", "python": platform.python_version(),
        "sqlite": sqlite3.sqlite_version,
        "scope": "Actual in-memory SQLite FTS5 BM25, eligibility predicates and sequential source changes. Not ANN, a separate tenant index, physical-read isolation, authentication or concurrent revocation.",
        "cases": reports}, indent=2))


if __name__ == "__main__":
    main()
