#!/usr/bin/env python3
"""Export JSON with array fields that are empty [] or contain null elements [null]
to CSV using an explicit array-emptiness column policy.

Usage:
  python array-emptiness-to-csv.py fixture.json output.csv naive.csv evidence.json
"""
import json, csv, sys, os, hashlib
from pathlib import Path

SCHEMA = [
    "id",
    "tags_state",
    "tags_element_count",
    "tags_null_count",
    "tags_value",
    "tags_json",
]

MAX_INPUT_BYTES = 1_000_000

def classify_array(arr):
    """Classify a JSON array value into state, counts, and serialised form."""
    elem_count = len(arr)
    null_count = sum(1 for v in arr if v is None)

    if elem_count == 0:
        state = "empty_array"
    elif all(v is None for v in arr):
        state = "all_null_elements"
    elif null_count > 0:
        state = "mixed_null_elements"
    else:
        state = "all_non_null"

    # value column: pipe-delimited repr of elements, using JSON for each element
    values = [json.dumps(v, ensure_ascii=False) for v in arr]
    value_col = "|".join(values)

    return state, elem_count, null_count, value_col


def export_explicit(source_path, csv_path):
    with open(source_path, "r", encoding="utf-8") as f:
        raw_text = f.read()

    if len(raw_text.encode("utf-8")) > MAX_INPUT_BYTES:
        raise ValueError(
            f"Input exceeds {MAX_INPUT_BYTES}-byte demonstration policy."
        )

    records = json.loads(raw_text)

    rows = []
    for rec in records:
        row = {col: "" for col in SCHEMA}
        row["id"] = rec["id"]
        tags = rec["tags"]

        if not isinstance(tags, list):
            raise ValueError(
                f"Row {rec['id']}: expected array, got {type(tags).__name__}"
            )

        state, elem_count, null_count, value_col = classify_array(tags)
        row["tags_state"] = state
        row["tags_element_count"] = elem_count
        row["tags_null_count"] = null_count
        row["tags_value"] = value_col
        row["tags_json"] = json.dumps(tags, ensure_ascii=False)
        rows.append(row)

    # Exclusive file creation: refuse to overwrite
    with open(csv_path, "x", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=SCHEMA)
        writer.writeheader()
        writer.writerows(rows)

    return rows, raw_text, records


def export_naive(source_path, naive_path):
    """Naive exporter that writes tags as a single column with str()."""
    with open(source_path, "r", encoding="utf-8") as f:
        records = json.loads(f.read())

    naive_schema = ["id", "tags"]
    rows = []
    for rec in records:
        tags = rec["tags"]
        if len(tags) == 0:
            naive_val = ""
        elif all(v is None for v in tags):
            naive_val = ",".join([""] * len(tags))
        else:
            naive_val = ",".join(str(v) if v is not None else "" for v in tags)
        rows.append({"id": rec["id"], "tags": naive_val})

    with open(naive_path, "x", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=naive_schema)
        writer.writeheader()
        writer.writerows(rows)


def read_back_verify(csv_path, records):
    """Read CSV back and compare every column against parsed source."""
    with open(csv_path, "r", encoding="utf-8", newline="") as f:
        reader = csv.DictReader(f)
        csv_rows = list(reader)

    results = []
    for csv_row, src_rec in zip(csv_rows, records):
        tags = src_rec["tags"]
        exp_state, exp_elem, exp_null, exp_value = classify_array(tags)
        exp_json = json.dumps(tags, ensure_ascii=False)

        checks = {
            "id": csv_row["id"] == src_rec["id"],
            "tags_state": csv_row["tags_state"] == exp_state,
            "tags_element_count": int(csv_row["tags_element_count"]) == exp_elem,
            "tags_null_count": int(csv_row["tags_null_count"]) == exp_null,
            "tags_value": csv_row["tags_value"] == exp_value,
            "tags_json": csv_row["tags_json"] == exp_json,
        }
        results.append({
            "row_id": src_rec["id"],
            "source_value": exp_json,
            "checks": checks,
            "all_passed": all(checks.values()),
        })

    return results


def main():
    if len(sys.argv) != 5:
        print("Usage: python array-emptiness-to-csv.py fixture.json output.csv naive.csv evidence.json")
        sys.exit(1)

    source_path = sys.argv[1]
    csv_path = sys.argv[2]
    naive_path = sys.argv[3]
    evidence_path = sys.argv[4]

    rows, raw_text, records = export_explicit(source_path, csv_path)
    export_naive(source_path, naive_path)
    verify_results = read_back_verify(csv_path, records)

    source_bytes = raw_text.encode("utf-8")
    source_sha = hashlib.sha256(source_bytes).hexdigest()
    with open(csv_path, "rb") as f:
        output_bytes = f.read()
    output_sha = hashlib.sha256(output_bytes).hexdigest()

    evidence = {
        "date": "2026-11-02",
        "python_version": sys.version,
        "source_sha256": source_sha,
        "source_bytes": len(source_bytes),
        "output_sha256": output_sha,
        "output_bytes": len(output_bytes),
        "schema": SCHEMA,
        "csv_data_rows": len(rows),
        "csv_columns": len(SCHEMA),
        "total_checks": sum(len(r["checks"]) for r in verify_results),
        "all_checks_passed": all(r["all_passed"] for r in verify_results),
        "per_row": verify_results,
    }

    with open(evidence_path, "x", encoding="utf-8") as f:
        json.dump(evidence, f, indent=2, ensure_ascii=False)

    print(f"Export complete: {len(rows)} rows, {len(SCHEMA)} columns")
    print(f"Total checks: {evidence['total_checks']}, all passed: {evidence['all_checks_passed']}")
    print(f"Source SHA-256: {source_sha[:16]}... ({len(source_bytes)} bytes)")
    print(f"Output SHA-256: {output_sha[:16]}... ({len(output_bytes)} bytes)")


if __name__ == "__main__":
    main()
