#!/usr/bin/env python3
"""JSON to CSV: Distinguish JSON number 0, JSON false, string "0" and string "false".

Explicit falsy-value column policy:
  flag_kind:         "number_zero" | "boolean_false" | "string_zero" | "string_false" | "string_other"
  flag_json_type:    "number" | "boolean" | "string"
  flag_value:        textual representation of the decoded value
  flag_json:         exact JSON serialization (0 for number, false for boolean, "0" for string, etc.)

A naive exporter writes all four falsy values into one bare `flag` column,
producing identical or near-identical cells that lose type identity.
"""
import json
import csv
import sys
import os
import hashlib
from pathlib import Path

SCHEMA = ["id", "flag_kind", "flag_json_type", "flag_value", "flag_json"]


def classify_value(val):
    """Classify a JSON value into kind, json_type, and textual value."""
    # bool must be checked BEFORE int because bool is a subclass of int in Python
    if isinstance(val, bool):
        if val is False:
            kind = "boolean_false"
        else:
            kind = "boolean_true"
        json_type = "boolean"
        value = "true" if val else "false"
    elif isinstance(val, int):
        if val == 0:
            kind = "number_zero"
        else:
            kind = "number_nonzero"
        json_type = "number"
        value = json.dumps(val)
    elif isinstance(val, float):
        kind = "number_float"
        json_type = "number"
        value = json.dumps(val)
    elif isinstance(val, str):
        if val == "0":
            kind = "string_zero"
        elif val == "false":
            kind = "string_false"
        elif val == "true":
            kind = "string_true"
        else:
            kind = "string_other"
        json_type = "string"
        value = val
    elif val is None:
        kind = "null"
        json_type = "null"
        value = "null"
    else:
        kind = type(val).__name__
        json_type = type(val).__name__
        value = json.dumps(val, ensure_ascii=False)
    return kind, json_type, value


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

    # Explicit policy rows
    rows = []
    for rec in records:
        row = {col: "" for col in SCHEMA}
        row["id"] = rec["id"]
        val = rec["flag"]
        kind, json_type, value = classify_value(val)
        row["flag_kind"] = kind
        row["flag_json_type"] = json_type
        row["flag_value"] = value
        row["flag_json"] = json.dumps(val, ensure_ascii=False)
        rows.append(row)

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

    # Naive export: single bare flag column
    naive_schema = ["id", "flag"]
    naive_rows = []
    for rec in records:
        val = rec["flag"]
        # Naive: just str() the value
        if isinstance(val, bool):
            naive_flag = "true" if val else "false"
        elif isinstance(val, str):
            naive_flag = val
        elif isinstance(val, (int, float)):
            naive_flag = str(val)
        elif val is None:
            naive_flag = ""
        else:
            naive_flag = str(val)
        naive_rows.append({"id": rec["id"], "flag": naive_flag})

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

    return rows, raw_text, records


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

    checks_passed = 0
    checks_total = 0
    for csv_row, src_rec in zip(csv_rows, records):
        val = src_rec["flag"]
        exp_kind, exp_type, exp_value = classify_value(val)
        exp_json = json.dumps(val, ensure_ascii=False)

        checks_total += 5
        assert csv_row["id"] == src_rec["id"], f"id mismatch: {csv_row['id']} != {src_rec['id']}"
        checks_passed += 1
        assert csv_row["flag_kind"] == exp_kind, f"kind mismatch R{csv_row['id']}: {csv_row['flag_kind']} != {exp_kind}"
        checks_passed += 1
        assert csv_row["flag_json_type"] == exp_type, f"type mismatch R{csv_row['id']}: {csv_row['flag_json_type']} != {exp_type}"
        checks_passed += 1
        assert csv_row["flag_value"] == exp_value, f"value mismatch R{csv_row['id']}: {csv_row['flag_value']} != {exp_value}"
        checks_passed += 1
        assert csv_row["flag_json"] == exp_json, f"json mismatch R{csv_row['id']}: {csv_row['flag_json']} != {exp_json}"
        checks_passed += 1

    return checks_passed, checks_total


def main():
    if len(sys.argv) < 5:
        print("Usage: python falsy-value-to-csv.py <input.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]

    # Read source for hashes
    with open(source_path, "rb") as f:
        source_bytes = f.read()
    source_sha256 = hashlib.sha256(source_bytes).hexdigest()
    source_size = len(source_bytes)

    rows, raw_text, records = export_explicit(source_path, csv_path, naive_path)

    checks_passed, checks_total = read_back_verify(csv_path, records)

    # Hash output
    with open(csv_path, "rb") as f:
        output_bytes = f.read()
    output_sha256 = hashlib.sha256(output_bytes).hexdigest()
    output_size = len(output_bytes)

    with open(naive_path, "rb") as f:
        naive_bytes = f.read()
    naive_sha256 = hashlib.sha256(naive_bytes).hexdigest()
    naive_size = len(naive_bytes)

    # Count rows and columns
    with open(csv_path, "r", encoding="utf-8", newline="") as f:
        reader = csv.DictReader(f)
        csv_rows = list(reader)
    data_rows = len(csv_rows)
    columns = len(SCHEMA)

    evidence = {
        "date": "2026-11-04",
        "python_version": "3.13.14",
        "source_sha256": source_sha256,
        "source_size_bytes": source_size,
        "output_sha256": output_sha256,
        "output_size_bytes": output_size,
        "naive_sha256": naive_sha256,
        "naive_size_bytes": naive_size,
        "data_rows": data_rows,
        "columns": columns,
        "checks_passed": checks_passed,
        "checks_total": checks_total,
        "schema": SCHEMA,
        "rows": [
            {
                "row": i,
                "id": row["id"],
                "flag_kind": row["flag_kind"],
                "flag_json_type": row["flag_json_type"],
                "flag_value": row["flag_value"],
                "flag_json": row["flag_json"],
            }
            for i, row in enumerate(rows)
        ],
    }

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

    print(f"Export complete: {data_rows} rows, {columns} columns, {output_size} bytes")
    print(f"Checks: {checks_passed}/{checks_total} passed")
    print(f"Source SHA-256: {source_sha256}")
    print(f"Output SHA-256: {output_sha256}")


if __name__ == "__main__":
    main()
