import argparse, csv, json, zipfile
from pathlib import Path

def reject_constant(token):
    raise ValueError("Non-JSON constant: " + token)

def unique_keys(pairs):
    row = {}
    for key, value in pairs:
        if key in row:
            raise ValueError("Duplicate JSON key: " + key)
        row[key] = value
    return row

def export(source, member, target):
    source, target = Path(source), Path(target)
    if source.resolve() == target.resolve():
        raise ValueError("Source and destination must differ")
    if source.stat().st_size > 2_000_000:
        raise ValueError("Demo archive cap exceeded")
    with zipfile.ZipFile(source, "r") as archive:
        entries = archive.infolist()
        if len(entries) > 50:
            raise ValueError("Demo member-count cap exceeded")
        matches = [entry for entry in entries if entry.filename == member]
        if len(matches) != 1:
            raise ValueError("Expected exactly one member named: " + member)
        info = matches[0]
        if info.is_dir():
            raise ValueError("Selected member is a directory")
        if info.flag_bits & 1:
            raise ValueError("Encrypted member is outside this recipe")
        if info.compress_type not in (zipfile.ZIP_STORED, zipfile.ZIP_DEFLATED):
            raise ValueError("Unsupported compression for this recipe")
        if info.file_size > 1_000_000:
            raise ValueError("Demo decoded-member cap exceeded")
        with archive.open(info, "r") as stream:
            raw = stream.read(1_000_001)
        if len(raw) > 1_000_000:
            raise ValueError("Demo decoded-member cap exceeded")
    rows = json.loads(raw.decode("utf-8", errors="strict"),
        object_pairs_hook=unique_keys, parse_constant=reject_constant)
    if not isinstance(rows, list):
        raise ValueError("Expected an array")
    for row in rows:
        if not isinstance(row, dict) or set(row) != {"id", "text"}:
            raise ValueError("Expected exact id/text objects")
        if not all(isinstance(row[k], str) for k in ("id", "text")):
            raise ValueError("id and text must be strings")
    with target.open("x", encoding="utf-8", newline="") as f:
        w = csv.writer(f, lineterminator="\n")
        w.writerow(["id", "text"])
        w.writerows((row["id"], row["text"]) for row in rows)

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("source")
    p.add_argument("target")
    p.add_argument("--member", required=True)
    args = p.parse_args()
    try:
        export(args.source, args.member, args.target)
    except (ValueError, OSError, RuntimeError, NotImplementedError,
            zipfile.BadZipFile, RecursionError) as e:
        p.exit(1, str(e) + "\n")
