#!/usr/bin/env python3
"""Compare SQL DBNames GUIDs with a 1C XML GUID index.

This report is a bridge between SQL storage-role records and XML metadata
objects. It keeps DBNames storage roles unchanged and only attaches XML facts
when the same GUID is present in the XML dump.
"""

from __future__ import annotations

import argparse
import json
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any


def load_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


def group_dbnames(dbnames_report: dict[str, Any]) -> dict[str, Any]:
    grouped: dict[str, Any] = defaultdict(lambda: {"records": [], "roles": Counter(), "sources": Counter()})
    for source in dbnames_report.get("dbnames") or []:
        file_name = source.get("file_name") or ""
        for record in source.get("records") or []:
            if record.get("status") != "parsed":
                continue
            guid = str(record.get("guid") or "").lower()
            if not guid:
                continue
            item = grouped[guid]
            compact = {
                "source_file": file_name,
                "storage_role": record.get("storage_role"),
                "sql_number": record.get("sql_number"),
                "index": record.get("index"),
            }
            item["records"].append(compact)
            item["roles"][compact["storage_role"]] += 1
            item["sources"][file_name] += 1
    result = {}
    for guid, item in grouped.items():
        result[guid] = {
            "records": sorted(item["records"], key=lambda row: (row["source_file"], str(row["storage_role"]), row["sql_number"])),
            "storage_roles": dict(sorted(item["roles"].items())),
            "source_files": dict(sorted(item["sources"].items())),
        }
    return dict(sorted(result.items()))


def main() -> int:
    parser = argparse.ArgumentParser(description="Compare SQL DBNames GUIDs with XML GUID index.")
    parser.add_argument("--dbnames", type=Path, required=True)
    parser.add_argument("--xml-index", type=Path, required=True)
    parser.add_argument("--output", type=Path, required=True)
    parser.add_argument("--max-objects", type=int, default=1000)
    args = parser.parse_args()

    dbnames = load_json(args.dbnames)
    xml_index = load_json(args.xml_index)
    sql_by_guid = group_dbnames(dbnames)
    xml_map = xml_index.get("guid_map") or {}

    matches = []
    top_object_matches = []
    occurrence_only_matches = []
    unmatched_sql = []
    matched_role_counts: Counter[str] = Counter()
    top_object_matched_role_counts: Counter[str] = Counter()
    occurrence_only_matched_role_counts: Counter[str] = Counter()
    unmatched_role_counts: Counter[str] = Counter()
    xml_kind_counts: Counter[str] = Counter()

    for guid, sql_item in sql_by_guid.items():
        xml_item = xml_map.get(guid)
        roles = sql_item["storage_roles"]
        if not xml_item:
            unmatched_sql.append({"guid": guid, "storage_roles": roles, "records": sql_item["records"][:20]})
            unmatched_role_counts.update(roles)
            continue
        top_objects = xml_item.get("top_objects") or []
        for role, count in roles.items():
            matched_role_counts[role] += count
            if top_objects:
                top_object_matched_role_counts[role] += count
            else:
                occurrence_only_matched_role_counts[role] += count
        for top_object in top_objects:
            xml_kind_counts[top_object.get("xml_kind") or "<unknown>"] += 1
        match_item = {
            "guid": guid,
            "storage_roles": roles,
            "source_files": sql_item["source_files"],
            "xml_top_objects": top_objects,
            "xml_occurrences": xml_item.get("occurrences") or [],
            "records": sql_item["records"][:50],
        }
        matches.append(match_item)
        if top_objects:
            top_object_matches.append(match_item)
        else:
            occurrence_only_matches.append(match_item)

    report = {
        "schema": "onec_sql_xml_guid_compare.v1",
        "dbnames": str(args.dbnames),
        "xml_index": str(args.xml_index),
        "sql_guid_count": len(sql_by_guid),
        "xml_guid_count": len(xml_map),
        "matched_guid_count": len(matches),
        "top_object_matched_guid_count": len(top_object_matches),
        "occurrence_only_matched_guid_count": len(occurrence_only_matches),
        "unmatched_sql_guid_count": len(unmatched_sql),
        "matched_guids": [item["guid"] for item in matches],
        "top_object_matched_guids": [item["guid"] for item in top_object_matches],
        "occurrence_only_matched_guids": [item["guid"] for item in occurrence_only_matches],
        "unmatched_sql_guids": [item["guid"] for item in unmatched_sql],
        "matched_storage_role_counts": dict(sorted(matched_role_counts.items(), key=lambda item: (-item[1], item[0]))),
        "top_object_matched_storage_role_counts": dict(
            sorted(top_object_matched_role_counts.items(), key=lambda item: (-item[1], item[0]))
        ),
        "occurrence_only_matched_storage_role_counts": dict(
            sorted(occurrence_only_matched_role_counts.items(), key=lambda item: (-item[1], item[0]))
        ),
        "unmatched_storage_role_counts": dict(sorted(unmatched_role_counts.items(), key=lambda item: (-item[1], item[0]))),
        "matched_xml_kind_counts": dict(sorted(xml_kind_counts.items(), key=lambda item: (-item[1], item[0]))),
        "matches": matches[: args.max_objects],
        "top_object_matches": top_object_matches[: args.max_objects],
        "occurrence_only_matches": occurrence_only_matches[: args.max_objects],
        "unmatched_sql": unmatched_sql[: args.max_objects],
    }
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(
        json.dumps(
            {
                "output": str(args.output),
                "sql_guids": len(sql_by_guid),
                "xml_guids": len(xml_map),
                "matched": len(matches),
                "top_object_matched": len(top_object_matches),
                "occurrence_only_matched": len(occurrence_only_matches),
                "unmatched_sql": len(unmatched_sql),
            },
            ensure_ascii=False,
        )
    )
    return 0


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