#!/usr/bin/env python3
"""Inspect a UTF-8 CSV locally and write a descriptive data-quality report."""

import argparse
import csv
import hashlib
import io
import json
import math
from pathlib import Path
import statistics
import sys

__version__ = "0.1.0"


class AuditError(ValueError):
    """Raised when an input cannot be inspected safely and unambiguously."""


def audit_csv(input_path):
    """Return a JSON-compatible report without modifying the input file."""
    input_path = Path(input_path)
    raw = input_path.read_bytes()
    try:
        decoded = raw.decode("utf-8-sig")
    except UnicodeDecodeError as exc:
        raise AuditError("Input must be UTF-8 encoded.") from exc

    reader = csv.reader(io.StringIO(decoded, newline=""), strict=True)
    try:
        header = next(reader, None)
        if not header:
            raise AuditError("Input must contain a nonempty header row.")
        names = [name.strip() for name in header]
        if any(not name for name in names):
            raise AuditError("Column names must not be blank.")
        if len(set(names)) != len(names):
            raise AuditError("Column names must be unique after trimming whitespace.")
        rows = []
        for row in reader:
            if len(row) != len(header):
                raise AuditError(
                    "CSV record ending on line {} has {} fields; expected {}.".format(
                        reader.line_num, len(row), len(header)
                    )
                )
            rows.append(row)
    except csv.Error as exc:
        raise AuditError("Malformed CSV near line {}: {}.".format(reader.line_num, exc)) from exc

    columns = []
    for index, name in enumerate(names):
        values = [row[index] for row in rows]
        present = [value.strip() for value in values if value.strip()]
        missing = len(values) - len(present)
        numeric = []
        nonfinite = 0
        all_numeric = bool(present)
        for value in present:
            try:
                number = float(value)
            except ValueError:
                all_numeric = False
                break
            if math.isfinite(number):
                numeric.append(number)
            else:
                nonfinite += 1
        stats = None
        if all_numeric:
            ordered = sorted(numeric)
            middle = len(ordered) // 2
            median = None
            if ordered:
                median = ordered[middle] if len(ordered) % 2 else statistics.mean(ordered[middle - 1:middle + 1])
            stats = {
                "finite_count": len(numeric),
                "nonfinite_count": nonfinite,
                "min": min(numeric) if numeric else None,
                "max": max(numeric) if numeric else None,
                "mean": statistics.mean(numeric) if numeric else None,
                "median": median,
            }
        columns.append({
            "name": name,
            "missing_count": missing,
            "missing_percent": round(missing / len(rows) * 100, 6) if rows else 0.0,
            "numeric": stats,
        })

    missing_total = sum(column["missing_count"] for column in columns)
    total_cells = len(rows) * len(header)
    return {
        "tool": "baseline",
        "version": __version__,
        "input": {
            "filename": input_path.name,
            "sha256": hashlib.sha256(raw).hexdigest(),
            "size_bytes": len(raw),
        },
        "summary": {
            "row_count": len(rows),
            "column_count": len(header),
            "cell_count": total_cells,
            "missing_cells": missing_total,
            "missing_percent": round(missing_total / total_cells * 100, 6) if total_cells else 0.0,
            "duplicate_rows": len(rows) - len({tuple(row) for row in rows}),
        },
        "columns": columns,
    }


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("input", type=Path, help="Path to a UTF-8, comma-separated CSV with a header")
    parser.add_argument("--output", type=Path, help="Write JSON to this path instead of standard output")
    parser.add_argument("--version", action="version", version="baseline " + __version__)
    args = parser.parse_args(argv)
    try:
        if args.output:
            same_path = args.input.resolve() == args.output.resolve()
            same_file = args.input.exists() and args.output.exists() and args.input.samefile(args.output)
            if same_path or same_file:
                raise AuditError("Output must not overwrite the input CSV.")
        report = audit_csv(args.input)
        payload = json.dumps(report, indent=2, ensure_ascii=False, allow_nan=False) + "\n"
        if args.output:
            args.output.write_text(payload, encoding="utf-8")
            print("Report written to {}".format(args.output))
        else:
            print(payload, end="")
    except (AuditError, OSError) as exc:
        print("Error: {}".format(exc), file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main())
