"""Convert a CSV file into JSON objects while preserving field values as strings."""
import argparse
import csv
import json
import sys
from pathlib import Path


def convert(source, destination, *, delimiter=None, encoding="utf-8-sig"):
    if delimiter is not None and (len(delimiter) != 1 or delimiter in "\r\n\0"):
        raise ValueError("Delimiter must be one non-newline, non-NUL character")
    with Path(source).open("r", encoding=encoding, newline="") as stream:
        if delimiter is None:
            sample = stream.read(65536)
            stream.seek(0)
            if not sample:
                rows = []
                dialect = None
            else:
                try:
                    dialect = csv.Sniffer().sniff(sample, delimiters=",;\t|")
                except csv.Error:
                    raise ValueError("Cannot determine CSV format; supply --delimiter explicitly")
        else:
            dialect = None
        if delimiter is not None or dialect is not None:
            reader = csv.reader(stream, dialect=dialect or "excel", strict=True,
                                **({"delimiter": delimiter} if delimiter is not None else {}))
            headers = next(reader, None)
            rows = []
            if headers is not None:
                if not headers or any(not header.strip() for header in headers):
                    raise ValueError("CSV headers must be non-empty")
                if len(set(headers)) != len(headers):
                    raise ValueError("Duplicate CSV headers would overwrite data")
                for record in reader:
                    if not record:
                        continue
                    if len(record) != len(headers):
                        raise ValueError(f"Record ending at line {reader.line_num} has {len(record)} fields; expected {len(headers)}")
                    rows.append(dict(zip(headers, record)))
    # Fully validate and serialize before creating the new output file.
    result = json.dumps(rows, ensure_ascii=False, indent=2) + "\n"
    with Path(destination).open("x", encoding="utf-8", newline="\n") as output:
        output.write(result)
    return len(rows)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("source")
    parser.add_argument("destination")
    parser.add_argument("--delimiter", help="One character; use 'tab' for a tab delimiter")
    parser.add_argument("--encoding", default="utf-8-sig")
    args = parser.parse_args()
    delimiter = "\t" if args.delimiter == "tab" else args.delimiter
    try:
        count = convert(args.source, args.destination, delimiter=delimiter, encoding=args.encoding)
    except (OSError, UnicodeError, LookupError, ValueError, csv.Error) as exc:
        print(f"Conversion failed: {exc}", file=sys.stderr)
        return 1
    print(f"Converted {count} rows to {args.destination}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
