from __future__ import annotations

import argparse
import json
from pathlib import Path
from typing import Any


XML_KIND_TO_SQL_KIND = {
    "Catalogs": "Catalog",
    "Documents": "Document",
    "Reports": "Report",
    "DataProcessors": "DataProcessor",
    "ChartsOfCharacteristicTypes": "ChartOfCharacteristicTypes",
    "ChartsOfAccounts": "ChartOfAccounts",
    "ChartsOfCalculationTypes": "ChartOfCalculationTypes",
    "InformationRegisters": "InformationRegister",
    "AccumulationRegisters": "AccumulationRegister",
    "AccountingRegisters": "AccountingRegister",
    "CalculationRegisters": "CalculationRegister",
    "BusinessProcesses": "BusinessProcess",
    "Tasks": "Task",
}


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


def xml_route(item: dict[str, Any]) -> dict[str, Any]:
    parts = Path(str(item.get("path") or item.get("relative_path") or "")).parts
    for index, part in enumerate(parts):
        if part in XML_KIND_TO_SQL_KIND and index + 3 < len(parts):
            owner_name = parts[index + 1]
            if parts[index + 2] == "Templates":
                return {
                    "owner_kind": XML_KIND_TO_SQL_KIND[part],
                    "owner_name": owner_name,
                    "template_name": parts[index + 3],
                    "source": "xml_path",
                }
    rel_parts = Path(str(item.get("relative_path") or "")).parts
    if len(rel_parts) >= 4 and rel_parts[0] == "Templates":
        return {"template_name": rel_parts[1], "source": "xml_relative_path"}
    return {"template_name": None, "source": "unknown"}


def sql_key(item: dict[str, Any]) -> tuple[str | None, str | None, str | None]:
    owner = item.get("owner") if isinstance(item.get("owner"), dict) else {}
    template = item.get("template") if isinstance(item.get("template"), dict) else {}
    return owner.get("kind"), owner.get("name"), template.get("name")


def xml_key(item: dict[str, Any]) -> tuple[str | None, str | None, str | None]:
    route = item.get("route") if isinstance(item.get("route"), dict) else xml_route(item)
    return route.get("owner_kind"), route.get("owner_name"), route.get("template_name")


def dimension_pair(value: Any) -> tuple[int | None, int | None]:
    if not isinstance(value, dict):
        return None, None
    rows = value.get("rows")
    columns = value.get("columns")
    try:
        parsed_rows = int(rows) if rows is not None else None
    except (TypeError, ValueError):
        parsed_rows = None
    try:
        parsed_columns = int(columns) if columns is not None else None
    except (TypeError, ValueError):
        parsed_columns = None
    return parsed_rows, parsed_columns


def dimension_delta(left: tuple[int | None, int | None], right: tuple[int | None, int | None]) -> dict[str, int | None]:
    rows = left[0] - right[0] if left[0] is not None and right[0] is not None else None
    columns = left[1] - right[1] if left[1] is not None and right[1] is not None else None
    return {"rows": rows, "columns": columns}


def format_dimensions(value: Any) -> str:
    rows, columns = dimension_pair(value)
    if rows is None and columns is None:
        return "-"
    return f"{rows if rows is not None else '-'}x{columns if columns is not None else '-'}"


def format_delta(value: dict[str, int | None] | None) -> str:
    if not isinstance(value, dict):
        return "-"
    rows = value.get("rows")
    columns = value.get("columns")
    if rows is None and columns is None:
        return "-"
    row_text = f"{rows:+d}" if isinstance(rows, int) else "-"
    column_text = f"{columns:+d}" if isinstance(columns, int) else "-"
    return f"{row_text}x{column_text}"


def sql_count(profile: dict[str, Any], key: str, sample_key: str | None = None) -> int | None:
    counts = profile.get("counts") if isinstance(profile.get("counts"), dict) else {}
    value = counts.get(key)
    if value is not None:
        try:
            return int(value)
        except (TypeError, ValueError):
            return None
    if sample_key and isinstance(profile.get(sample_key), list):
        return len(profile.get(sample_key) or [])
    return None


def merge_record_analysis_counts(profile: dict[str, Any]) -> dict[str, int]:
    blocks = 0
    records = 0
    for candidate in profile.get("sample_merge_record_block_candidates") or []:
        if not isinstance(candidate, dict):
            continue
        evidence = candidate.get("evidence") if isinstance(candidate.get("evidence"), dict) else {}
        analysis = evidence.get("record_analysis") if isinstance(evidence.get("record_analysis"), dict) else {}
        if analysis.get("schema") != "moxel_numeric_block_records.v1":
            continue
        blocks += 1
        try:
            records += int(analysis.get("records_analyzed") or 0)
        except (TypeError, ValueError):
            pass
    return {"blocks": blocks, "records": records}


def compare_item(sql_item: dict[str, Any] | None, xml_item: dict[str, Any]) -> dict[str, Any]:
    route = xml_item.get("route") if isinstance(xml_item.get("route"), dict) else xml_route(xml_item)
    xml_counts = xml_item.get("counts") if isinstance(xml_item.get("counts"), dict) else {}
    xml_capacity = dimension_pair(xml_item.get("capacity_dimensions"))
    xml_used = dimension_pair(xml_item.get("used_dimensions"))
    result: dict[str, Any] = {
        "route": route,
        "xml": {
            "xml_kind": xml_item.get("xml_kind"),
            "capacity_dimensions": xml_item.get("capacity_dimensions"),
            "used_dimensions": xml_item.get("used_dimensions"),
            "counts": xml_counts,
        },
        "status": "ok",
        "gaps": [],
    }
    if sql_item is None:
        result["status"] = "missing_sql_profile"
        result["gaps"].append({"code": "missing_sql_profile", "severity": "error", "message": "No matching SQL template profile was found."})
        return result

    profile = sql_item.get("profile") if isinstance(sql_item.get("profile"), dict) else {}
    sql_capacity = dimension_pair(profile.get("capacity_dimensions") or profile.get("dimensions"))
    sql_used = dimension_pair(profile.get("used_dimensions"))
    used_delta = dimension_delta(sql_used, xml_used)
    sql_counts = {
        "cells": sql_count(profile, "cells", None),
        "cell_coordinate_hints": sql_count(profile, "cell_coordinate_hints", "sample_cell_coordinate_hints"),
        "cell_style_coordinate_hints": sql_count(profile, "cell_style_coordinate_hints", "sample_style_coordinate_hints"),
        "parameters": sql_count(profile, "cell_parameters", "sample_cell_parameters"),
        "text_ids": sql_count(profile, "cell_text_identifiers", "sample_text_identifiers"),
        "merges": sql_count(profile, "merged_ranges", None),
        "merge_record_block_candidates": sql_count(profile, "merge_record_block_candidates", "sample_merge_record_block_candidates"),
        "merge_count_hints": sql_count(profile, "merge_count_hints", "sample_merge_count_hints"),
        "format_indexes": sql_count(profile, "cell_style_candidates", "sample_style_texts"),
    }
    merge_record_analysis = merge_record_analysis_counts(profile)
    result["sql"] = {
        "owner": sql_item.get("owner"),
        "template": sql_item.get("template"),
        "capacity_dimensions": profile.get("capacity_dimensions") or profile.get("dimensions"),
        "used_dimensions": profile.get("used_dimensions"),
        "used_delta": used_delta,
        "counts": sql_counts,
        "merge_record_analysis": merge_record_analysis,
        "capabilities": profile.get("capabilities") or {},
    }

    if sql_capacity != xml_capacity:
        result["gaps"].append(
            {
                "code": "capacity_dimensions_mismatch",
                "severity": "error",
                "sql": {"rows": sql_capacity[0], "columns": sql_capacity[1]},
                "xml": {"rows": xml_capacity[0], "columns": xml_capacity[1]},
            }
        )
    if sql_used == (None, None):
        result["gaps"].append({"code": "used_dimensions_missing", "severity": "warning", "xml": {"rows": xml_used[0], "columns": xml_used[1]}})
    elif sql_used != xml_used:
        result["gaps"].append(
            {
                "code": "used_dimensions_mismatch",
                "severity": "warning",
                "sql": {"rows": sql_used[0], "columns": sql_used[1]},
                "xml": {"rows": xml_used[0], "columns": xml_used[1]},
            }
        )

    checks = [
        ("cells", "cells_missing_or_limited"),
        ("parameters", "parameters_missing_or_limited"),
        ("merges", "merged_ranges_missing"),
        ("format_indexes", "format_indexes_missing_or_limited"),
    ]
    for key, code in checks:
        xml_value = xml_counts.get(key)
        sql_value = sql_counts.get(key)
        if not xml_value:
            continue
        if sql_value in {None, 0}:
            result["gaps"].append({"code": code, "severity": "warning", "sql": sql_value, "xml": xml_value})
        elif int(sql_value) < int(xml_value):
            result["gaps"].append({"code": code, "severity": "info", "sql": sql_value, "xml": xml_value})

    hint_count = int(sql_counts.get("cell_coordinate_hints") or 0)
    xml_cells = int(xml_counts.get("cells") or 0)
    if xml_cells and hint_count:
        result.setdefault("progress", []).append(
            {
                "code": "cell_coordinate_hints_available",
                "sql": hint_count,
                "xml_cells": xml_cells,
                "coverage_ratio": round(hint_count / xml_cells, 4),
                "message": "SQL decoder returned coordinate hints. These are progress evidence, not authoritative decoded cells.",
            }
        )
    merge_block_count = int(sql_counts.get("merge_record_block_candidates") or 0)
    merge_count_hint_count = int(sql_counts.get("merge_count_hints") or 0)
    xml_merges = int(xml_counts.get("merges") or 0)
    if xml_merges and merge_block_count:
        result.setdefault("progress", []).append(
            {
                "code": "merge_record_blocks_available",
                "sql": merge_block_count,
                "xml_merges": xml_merges,
                "message": "SQL decoder found MOXCEL merge-record block candidates. These are progress evidence, not authoritative merged ranges.",
            }
        )
    if xml_merges and merge_count_hint_count:
        result.setdefault("progress", []).append(
            {
                "code": "merge_count_hints_available",
                "sql": merge_count_hint_count,
                "xml_merges": xml_merges,
                "message": "SQL decoder found MOXCEL merge-count hints. These confirm merge presence/count slots, not authoritative merged ranges.",
            }
        )
    if xml_merges and merge_record_analysis["blocks"]:
        result.setdefault("progress", []).append(
            {
                "code": "merge_record_analysis_available",
                "sql_blocks": merge_record_analysis["blocks"],
                "sql_records_analyzed": merge_record_analysis["records"],
                "xml_merges": xml_merges,
                "message": "SQL decoder returned normalized merge-block record analysis for slot-formula discovery.",
            }
        )

    if result["gaps"]:
        result["status"] = "gap"
    return result


def compare(sql_profile: dict[str, Any], xml_profile: dict[str, Any]) -> dict[str, Any]:
    sql_items = [item for item in sql_profile.get("items") or [] if isinstance(item, dict)]
    sql_by_key = {sql_key(item): item for item in sql_items}
    comparisons = []
    for xml_item in xml_profile.get("templates") or []:
        if not isinstance(xml_item, dict) or xml_item.get("xml_kind") != "tabular_document":
            continue
        route = xml_route(xml_item)
        xml_item = {**xml_item, "route": route}
        key = xml_key(xml_item)
        sql_item = sql_by_key.get(key)
        if sql_item is None and key[2]:
            matches = [item for item in sql_items if sql_key(item)[2] == key[2]]
            sql_item = matches[0] if len(matches) == 1 else None
        comparisons.append(compare_item(sql_item, xml_item))
    gap_counts: dict[str, int] = {}
    progress_counts: dict[str, int] = {}
    for item in comparisons:
        for gap in item.get("gaps") or []:
            code = str(gap.get("code") or "unknown")
            gap_counts[code] = gap_counts.get(code, 0) + 1
        for progress in item.get("progress") or []:
            code = str(progress.get("code") or "unknown")
            progress_counts[code] = progress_counts.get(code, 0) + 1
    return {
        "schema": "codex_1c_template_sql_xml_profile_compare.v1",
        "source": "analysis_only_xml_fixture",
        "sql_profile": sql_profile.get("base_id") or sql_profile.get("schema"),
        "xml_profile": xml_profile.get("root"),
        "comparisons": comparisons,
        "counts": {
            "xml_tabular_templates": len(comparisons),
            "matched": sum(1 for item in comparisons if item.get("sql")),
            "missing_sql_profile": sum(1 for item in comparisons if item.get("status") == "missing_sql_profile"),
            "with_gaps": sum(1 for item in comparisons if item.get("gaps")),
            "gap_counts": gap_counts,
            "progress_counts": progress_counts,
        },
    }


def render_markdown(payload: dict[str, Any]) -> str:
    lines = ["# 1C Template SQL/XML Profile Compare", ""]
    counts = payload.get("counts") or {}
    lines.append(f"- XML tabular templates: `{counts.get('xml_tabular_templates')}`")
    lines.append(f"- Matched SQL profiles: `{counts.get('matched')}`")
    lines.append(f"- With gaps: `{counts.get('with_gaps')}`")
    lines.append(f"- Progress signals: `{counts.get('progress_counts') or {}}`")
    lines.append("")
    lines.append(
        "| Owner | Template | Status | SQL capacity | XML capacity | SQL used | XML used | Used delta | SQL cells | Coord hints | Merge blocks | Merge count hints | Merge records analyzed | Gaps | Progress |"
    )
    lines.append("| --- | --- | --- | --- | --- | --- | --- | --- | ---: | ---: | ---: | ---: | ---: | --- | --- |")
    for item in payload.get("comparisons") or []:
        route = item.get("route") or {}
        sql = item.get("sql") or {}
        xml = item.get("xml") or {}
        sql_counts = sql.get("counts") or {}
        merge_record_analysis = sql.get("merge_record_analysis") or {}
        gaps = ", ".join(str(gap.get("code")) for gap in (item.get("gaps") or []))
        progress = ", ".join(str(entry.get("code")) for entry in (item.get("progress") or []))
        lines.append(
            f"| `{route.get('owner_kind') or ''}.{route.get('owner_name') or ''}` | "
            f"`{route.get('template_name') or ''}` | `{item.get('status')}` | "
            f"`{format_dimensions(sql.get('capacity_dimensions'))}` | "
            f"`{format_dimensions(xml.get('capacity_dimensions'))}` | "
            f"`{format_dimensions(sql.get('used_dimensions'))}` | "
            f"`{format_dimensions(xml.get('used_dimensions'))}` | "
            f"`{format_delta(sql.get('used_delta'))}` | "
            f"{sql_counts.get('cells') or 0} | {sql_counts.get('cell_coordinate_hints') or 0} | "
            f"{sql_counts.get('merge_record_block_candidates') or 0} | {sql_counts.get('merge_count_hints') or 0} | "
            f"{merge_record_analysis.get('records') or 0} | "
            f"`{gaps}` | `{progress}` |"
        )
    lines.append("")
    return "\n".join(lines)


def main() -> int:
    parser = argparse.ArgumentParser(description="Compare SQL-decoded template profiles with XML analysis fixtures.")
    parser.add_argument("--sql-profile", default="reports/1c-template-baselines/upo_test_tabular_template_profiles.json")
    parser.add_argument("--xml-profile", required=True)
    parser.add_argument("--output-json", default="reports/1c-template-baselines/sql-xml-template-profile-compare.json")
    parser.add_argument("--output-markdown", default="reports/1c-template-baselines/sql-xml-template-profile-compare.md")
    args = parser.parse_args()

    payload = compare(read_json(Path(args.sql_profile)), read_json(Path(args.xml_profile)))
    json_path = Path(args.output_json)
    md_path = Path(args.output_markdown)
    json_path.parent.mkdir(parents=True, exist_ok=True)
    md_path.parent.mkdir(parents=True, exist_ok=True)
    json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    md_path.write_text(render_markdown(payload), encoding="utf-8")
    print(json.dumps({"status": "ok", "json": str(json_path), "markdown": str(md_path), "counts": payload["counts"]}, ensure_ascii=False, indent=2))
    return 0


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