"""
validation.py — deterministic validators that run AFTER extraction.

In a bank you never trust an extracted money/identity field on confidence alone;
you cross-check it with hard rules (Knowledge Module 04 §5, §7). These are real,
testable algorithms — the IBAN mod-97 check below is the genuine ISO 13616 one.
"""
from __future__ import annotations

# Eastern Arabic numerals -> Western, so OCR'd Arabic documents validate/compute
# correctly (K05 §6). Always normalize before any numeric check.
_EASTERN = {"٠": "0", "١": "1", "٢": "2", "٣": "3", "٤": "4",
            "٥": "5", "٦": "6", "٧": "7", "٨": "8", "٩": "9"}


def normalize_digits(s: str) -> str:
    return "".join(_EASTERN.get(ch, ch) for ch in s)


def iban_is_valid(iban: str) -> bool:
    """ISO 13616 mod-97 check. Move the first 4 chars to the end, map letters to
    numbers (A=10..Z=35), interpret as an integer, and verify mod 97 == 1. This
    catches a mistyped/misread IBAN before money is sent to the wrong account."""
    iban = normalize_digits(iban).replace(" ", "").upper()
    if len(iban) < 15 or not iban[:2].isalpha():
        return False
    rearranged = iban[4:] + iban[:4]
    digits = "".join(str(ord(c) - 55) if c.isalpha() else c for c in rearranged)
    if not digits.isdigit():
        return False
    return int(digits) % 97 == 1


def statement_balance_consistent(opening: float, transactions: list[float],
                                 closing: float, tol: float = 0.01) -> bool:
    """opening + sum(transactions) must equal closing (within rounding). A failure
    means a row was mis-extracted — route to human review, never auto-post."""
    return abs((opening + sum(transactions)) - closing) <= tol


def date_not_expired(expiry_yyyy_mm_dd: str, today: str = "2026-06-14") -> bool:
    """Simple ISO date comparison (string compare works for YYYY-MM-DD)."""
    return normalize_digits(expiry_yyyy_mm_dd) >= today
