#!/usr/bin/env python3
"""Сверка двух JSON-выгрузок.

Читает два JSON-массива записей с полями id и amount и пишет два CSV:

* основной файл — уникальные записи (одинаковые дубли по id и amount убраны,
  source перечисляет все источники, где запись встретилась);
* файл конфликтов — записи с одним id, но разными amount, по строке на каждый
  вариант значения.

Использование:
    python3 скрипт-сверки-выгрузок.py выгрузка-а.json выгрузка-б.json \
        --out результаты/сверка-выгрузок.csv \
        --conflicts результаты/конфликты-выгрузок.csv
"""

from __future__ import annotations

import argparse
import csv
import json
import sys
from collections import defaultdict
from pathlib import Path

FIELDS = ("id", "amount", "source")


def load_records(path: Path) -> list[dict]:
    """Читает JSON-массив записей, добавляя имя файла как источник."""
    try:
        raw = json.loads(path.read_text(encoding="utf-8"))
    except FileNotFoundError:
        sys.exit(f"Файл не найден: {path}")
    except json.JSONDecodeError as exc:
        sys.exit(f"Некорректный JSON в {path}: {exc}")

    if not isinstance(raw, list):
        sys.exit(f"{path}: ожидался JSON-массив записей")

    source = path.stem
    records = []
    for number, item in enumerate(raw, start=1):
        if not isinstance(item, dict) or "id" not in item or "amount" not in item:
            sys.exit(f"{path}, запись {number}: нужны поля id и amount")
        records.append({"id": str(item["id"]), "amount": item["amount"], "source": source})
    return records


def amount_key(amount: object) -> str:
    """Сравнимый ключ значения: null, числа и строки различаются единообразно."""
    return "" if amount is None else str(amount)


def split(records: list[dict]) -> tuple[list[dict], list[dict]]:
    """Делит записи на уникальные и конфликтующие."""
    by_id: dict[str, list[dict]] = defaultdict(list)
    for record in records:
        by_id[record["id"]].append(record)

    unique: list[dict] = []
    conflicts: list[dict] = []

    for identifier in sorted(by_id):
        variants: dict[str, dict] = {}
        for record in by_id[identifier]:
            key = amount_key(record["amount"])
            variant = variants.setdefault(key, {"amount": record["amount"], "sources": []})
            if record["source"] not in variant["sources"]:
                variant["sources"].append(record["source"])

        if len(variants) == 1:
            variant = next(iter(variants.values()))
            unique.append(
                {
                    "id": identifier,
                    "amount": variant["amount"],
                    "source": "+".join(variant["sources"]),
                }
            )
        else:
            for key in sorted(variants):
                variant = variants[key]
                conflicts.append(
                    {
                        "id": identifier,
                        "amount": variant["amount"],
                        "source": "+".join(variant["sources"]),
                    }
                )

    return unique, conflicts


def write_csv(path: Path, rows: list[dict]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8-sig", newline="") as handle:
        writer = csv.DictWriter(handle, fieldnames=FIELDS)
        writer.writeheader()
        for row in rows:
            writer.writerow(
                {
                    "id": row["id"],
                    "amount": "" if row["amount"] is None else row["amount"],
                    "source": row["source"],
                }
            )


def main() -> None:
    parser = argparse.ArgumentParser(description="Сверка двух JSON-выгрузок в CSV.")
    parser.add_argument("first", type=Path, help="первый JSON-файл")
    parser.add_argument("second", type=Path, help="второй JSON-файл")
    parser.add_argument(
        "--out",
        type=Path,
        default=Path("сверка-выгрузок.csv"),
        help="CSV с уникальными записями (по умолчанию сверка-выгрузок.csv)",
    )
    parser.add_argument(
        "--conflicts",
        type=Path,
        default=Path("конфликты-выгрузок.csv"),
        help="CSV с конфликтующими записями (по умолчанию конфликты-выгрузок.csv)",
    )
    args = parser.parse_args()

    records = load_records(args.first) + load_records(args.second)
    unique, conflicts = split(records)

    write_csv(args.out, unique)
    write_csv(args.conflicts, conflicts)

    print(f"Прочитано записей: {len(records)}")
    print(f"Уникальные записи: {len(unique)} -> {args.out}")
    print(f"Конфликтующие записи: {len(conflicts)} -> {args.conflicts}")


if __name__ == "__main__":
    main()
