"""Export selected SQLite table columns from a consistent, read-only snapshot."""
import argparse
import json
import sqlite3
import time
from pathlib import Path


def collect(database, selection):
    path = Path(database).resolve(strict=True)
    if not isinstance(selection, dict) or not selection or len(selection) > 50:
        raise ValueError('Select between 1 and 50 tables.')
    deadline = time.monotonic() + 10
    connection = sqlite3.connect(path.as_uri() + '?mode=ro', uri=True, timeout=3)
    try:
        if hasattr(connection, 'setlimit'):
            connection.setlimit(sqlite3.SQLITE_LIMIT_LENGTH, 2_000_000)
        connection.execute('PRAGMA query_only=ON')
        connection.execute('PRAGMA trusted_schema=OFF')
        connection.set_progress_handler(lambda: int(time.monotonic() > deadline), 1000)
        connection.execute('BEGIN')
        tables = {}; total = 0; encoded_size = 2
        quote = lambda value: '"' + value.replace('"', '""') + '"'
        for table, columns in selection.items():
            if not isinstance(table, str) or not isinstance(columns, list) or not columns or len(columns) > 100 or any(not isinstance(c, str) or not c for c in columns) or len(set(columns)) != len(columns):
                raise ValueError('Each table needs a unique list of column names.')
            match = connection.execute('SELECT type,sql FROM sqlite_schema WHERE name=?', (table,)).fetchone()
            if not match or match[0] != 'table' or (match[1] or '').upper().startswith('CREATE VIRTUAL'):
                raise ValueError('Select ordinary tables only.')
            known = {row[1] for row in connection.execute('PRAGMA table_info(' + quote(table) + ')')}
            if not set(columns) <= known:
                raise ValueError('Selected column is absent.')
            rows = connection.execute('SELECT ' + ','.join(map(quote, columns)) + ' FROM ' + quote(table) + ' LIMIT ?', (10001-total,))
            encoded_size += len(json.dumps(table, ensure_ascii=False).encode('utf-8')) + 6
            tables[table] = []
            for row in rows:
                if time.monotonic() > deadline:
                    raise ValueError('Snapshot exceeded ten seconds.')
                total += 1
                if total > 10000:
                    raise ValueError('Selection exceeds 10,000 rows; create a smaller export explicitly.')
                record = dict(zip(columns, row))
                encoded_size += len(json.dumps(record, ensure_ascii=False, allow_nan=False).encode('utf-8')) + 2
                if encoded_size > 2_000_000:
                    raise ValueError('Export exceeds 2 MB; select fewer rows or columns.')
                tables[table].append(record)
        return tables
    finally:
        connection.close()


def main():
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument('database'); p.add_argument('spec'); p.add_argument('output'); a=p.parse_args()
    spec_path=Path(a.spec)
    if spec_path.stat().st_size>100000: raise ValueError('Specification too large.')
    spec=json.loads(spec_path.read_text())
    result={'tables':collect(a.database,spec['tables']), 'rules':spec['rules']}
    encoded=json.dumps(result,ensure_ascii=False,allow_nan=False).encode('utf-8')
    if len(encoded)>2_000_000: raise ValueError('Export exceeds 2 MB.')
    with open(a.output,'xb') as f: f.write(encoded)
    print('Read-only snapshot saved. Review sensitive data before importing.')

if __name__=='__main__':
    try: main()
    except (ValueError,TypeError,KeyError,OSError,sqlite3.Error):
        raise SystemExit('Snapshot failed. Check the database, selected columns, JSON and limits; no database changes were made.')
