from __future__ import annotations

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


MARKER_RE = re.compile(r"^(\d+)-(\d+)$")


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


def as_int(value: Any) -> int | None:
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


def marker_cells(probe: dict[str, Any]) -> list[dict[str, Any]]:
    data = probe.get("probe") if isinstance(probe.get("probe"), dict) else probe
    cells = data.get("cells") if isinstance(data.get("cells"), list) else []
    result = []
    for cell in cells:
        if not isinstance(cell, dict):
            continue
        match = MARKER_RE.match(str(cell.get("text") or ""))
        if not match:
            continue
        expected_row, expected_col = map(int, match.groups())
        one_based = cell.get("one_based") if isinstance(cell.get("one_based"), dict) else {}
        result.append(
            {
                "text": cell.get("text"),
                "expected_row": expected_row,
                "expected_col": expected_col,
                "decoded_row": one_based.get("row"),
                "decoded_col": one_based.get("column"),
                "ok": one_based.get("row") == expected_row and one_based.get("column") == expected_col,
            }
        )
    return result


def verify_inline_column_rule(probe: dict[str, Any]) -> dict[str, Any]:
    samples = marker_cells(probe)
    ok = sum(1 for sample in samples if sample.get("ok") is True)
    return {
        "target": "moxel.inline_text_cell.column",
        "status": "ok" if samples and ok == len(samples) else "failed",
        "evidence": {"ok": ok, "total": len(samples)},
        "failed_samples": [sample for sample in samples if sample.get("ok") is not True],
    }


def named_ranges(probe: dict[str, Any]) -> list[dict[str, Any]]:
    data = probe.get("probe") if isinstance(probe.get("probe"), dict) else probe
    ranges = data.get("named_ranges") or data.get("named_range_candidates") or []
    return [item for item in ranges if isinstance(item, dict)]


def raw_scalar(raw: list[Any], index: int) -> int | None:
    if index < 0 or index >= len(raw):
        return None
    return as_int(raw[index])


def verify_named_range_candidate(rule: dict[str, Any], probe: dict[str, Any]) -> dict[str, Any]:
    target = str(rule.get("target") or "")
    field = target.rsplit(".", 1)[-1]
    indexes = rule.get("raw_scalar_indexes") if isinstance(rule.get("raw_scalar_indexes"), list) else []
    samples = []
    for item in named_ranges(probe):
        range_info = item.get("range") if isinstance(item.get("range"), dict) else {}
        one_based = range_info.get("one_based") if isinstance(range_info.get("one_based"), dict) else {}
        expected = as_int(one_based.get(field))
        raw = item.get("raw_scalars") if isinstance(item.get("raw_scalars"), list) else []
        if expected is None or not raw:
            continue
        predictions = []
        for index in indexes:
            index_int = as_int(index)
            if index_int is None:
                continue
            value = raw_scalar(raw, index_int)
            predictions.append({"index": index_int, "value": value, "one_based": value + 1 if value is not None else None})
        matching = [prediction for prediction in predictions if prediction.get("one_based") == expected]
        samples.append(
            {
                "name": item.get("name"),
                "kind": item.get("kind"),
                "field": field,
                "expected": expected,
                "predictions": predictions,
                "ok": bool(matching),
            }
        )
    ok = sum(1 for sample in samples if sample.get("ok") is True)
    return {
        "target": target,
        "status": "ok" if samples and ok == len(samples) else "inconclusive" if not samples else "partial",
        "evidence": {"ok": ok, "total": len(samples)},
        "samples": samples,
    }


def verify_registry(registry: dict[str, Any], probes: list[dict[str, Any]]) -> dict[str, Any]:
    results = []
    for rule in registry.get("rules") or []:
        if not isinstance(rule, dict):
            continue
        target = rule.get("target")
        per_probe = []
        for probe in probes:
            if target == "moxel.inline_text_cell.column":
                per_probe.append(verify_inline_column_rule(probe))
            elif str(target or "").startswith("moxel.named_range."):
                per_probe.append(verify_named_range_candidate(rule, probe))
        if not per_probe:
            continue
        status = "ok" if all(item.get("status") == "ok" for item in per_probe) else "partial" if any(item.get("status") in {"ok", "partial"} for item in per_probe) else "failed"
        results.append({"rule_id": rule.get("id"), "target": target, "registry_read_status": rule.get("read_status"), "verification_status": status, "probes": per_probe})
    failures = [
        item
        for item in results
        if item.get("registry_read_status") == "verified_read" and item.get("verification_status") != "ok"
    ]
    return {
        "schema": "codex_1c_moxel_schema_registry_verification.v1",
        "status": "ok" if not failures else "failed",
        "results": results,
        "failures": failures,
        "counts": {
            "rules_checked": len(results),
            "verified_read_failures": len(failures),
        },
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Verify 1C MOXCEL schema registry rules against probe snapshots.")
    parser.add_argument("--registry", default="plugins/1c/metadata/moxel-schema-registry.json")
    parser.add_argument("--probe", action="append", required=True, help="Probe snapshot JSON. Repeatable.")
    parser.add_argument("--output", default="reports/1c-template-baselines/moxel-schema-registry-verification.json")
    args = parser.parse_args()

    report = verify_registry(read_json(Path(args.registry)), [read_json(Path(path)) for path in args.probe])
    output_path = Path(args.output)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    print(json.dumps(report, ensure_ascii=False, indent=2))
    return 0 if report["status"] == "ok" else 1


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