#!/usr/bin/env python3
"""Narrow offline Japanese price parser; no HTTP, tax calculation or page selection."""
from __future__ import annotations

import argparse
import json
from pathlib import Path
import re
import sys
import unicodedata

AMOUNT = r"(?:0|[1-9][0-9]*|[1-9][0-9]{0,2}(?:,[0-9]{3})+)"
PRICE = rf"(?:¥\s*(?P<yen>{AMOUNT})|(?P<suffix>{AMOUNT})\s*円|JPY\s*(?P<code>{AMOUNT}))"
TAX = r"(?:税込価格|税込|税抜き|税抜|税別)"
PATTERN = re.compile(rf"{PRICE}(?:\s*\((?P<tax>{TAX})\)|\s+(?P<bare_tax>{TAX}))?")
ALLOWED_KEYS = {"text", "currency", "currency_evidence"}


def parse_price(record: dict) -> dict:
    """Parse a selected single-price field; caller supplies independent currency evidence."""
    if not isinstance(record, dict) or set(record) - ALLOWED_KEYS:
        return {"status": "review", "reason": "invalid_record"}
    text = record.get("text")
    if not isinstance(text, str) or not 1 <= len(text) <= 200:
        return {"status": "review", "reason": "invalid_text"}
    result = {"original": text, "status": "review"}
    if record.get("currency") != "JPY":
        return {**result, "reason": "missing_or_non_jpy_currency"}
    evidence = record.get("currency_evidence")
    if not isinstance(evidence, str) or not evidence.strip():
        return {**result, "reason": "missing_currency_evidence"}
    # NFKC folds more than width. Reject other compatibility changes first;
    # a superscript or circled digit must not silently become part of a price.
    for ch in text:
        width_character = 0xFF01 <= ord(ch) <= 0xFF5E or ch in "\u3000\uffe5"
        if unicodedata.normalize("NFKC", ch) != ch and not width_character:
            return {**result, "reason": "unsupported_compatibility_character"}
    normalized = unicodedata.normalize("NFKC", text).strip()
    result["normalized"] = normalized
    match = PATTERN.fullmatch(normalized)
    if not match:
        return {**result, "reason": "unsupported_or_multiple_price_text"}
    amount = next(match.group(key) for key in ("yen", "suffix", "code") if match.group(key) is not None)
    tax = match.group("tax") or match.group("bare_tax")
    basis = "included" if tax in ("税込", "税込価格") else "excluded" if tax else "unspecified"
    return {
        **result,
        "status": "parsed",
        "amount_yen": int(amount.replace(",", "")),
        "currency": "JPY",
        "currency_evidence": evidence,
        "tax_basis": basis,
        "tax_label": tax,
    }


def replay(path: Path) -> dict:
    cases = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(cases, list) or not cases:
        raise ValueError("fixture must be a nonempty array")
    rows = []
    seen = set()
    for case in cases:
        if not isinstance(case, dict) or set(case) != {"id", "input", "expected"}:
            raise ValueError("invalid fixture case")
        if not isinstance(case["id"], str) or not case["id"] or case["id"] in seen:
            raise ValueError("missing or duplicate case id")
        seen.add(case["id"])
        actual = parse_price(case["input"])
        expected = case["expected"]
        if not isinstance(expected, dict) or expected.get("status") not in {"parsed", "review"}:
            raise ValueError("each case needs an expected classification")
        required = {"amount_yen", "tax_basis"} if expected["status"] == "parsed" else {"reason"}
        if not required <= expected.keys():
            raise ValueError("expected classification needs its decision fields")
        passed = all(actual.get(key) == value for key, value in expected.items())
        rows.append({"id": case["id"], "passed": passed, "actual": actual, "expected": expected})
    return {"kind": "synthetic_offline", "cases": len(rows), "passed": sum(row["passed"] for row in rows), "failed": sum(not row["passed"] for row in rows), "results": rows}


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("fixture", type=Path)
    args = parser.parse_args()
    try:
        result = replay(args.fixture)
    except (OSError, ValueError, KeyError, TypeError) as error:
        print(f"Invalid fixture: {type(error).__name__}", file=sys.stderr)
        return 2
    print(json.dumps(result, ensure_ascii=False, indent=2))
    return int(result["failed"] != 0)


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