#!/usr/bin/env python3
"""Offline, Python 3.9+ stdlib verifier for ONE pinned historical comparison.

No networking, RIS parser, payment, tax calculation or individual eligibility.
Boundary predicates are NOT a current-law selector: 'after' means only that
this amendment's historical boundary is crossed, not that its text is still law.
The embedded digest detects fixture drift; it is NOT a signature or independent
authentication of official sources. Use --diff to compare full statutory text.
"""

import argparse
from datetime import date
import difflib
import hashlib
import json
from pathlib import Path
import re
import sys

COMPARISON_ID = "at-bgbl-i-2024-110-estg-26-9a"
FIXTURE_SHA256 = "b754bfb7bda481462054b4705f6e4586c88d5e86a21c11e89b9fc404f64b1efe"
LIMITATION = (
    "Historical amendment boundary only; not current law, individual eligibility, "
    "tax calculation, or certification of the selected text for all earlier/later periods."
)
MAX_FIXTURE_BYTES = 100_000


class FixtureError(ValueError):
    """Malformed or changed historical fixture."""


def require(condition, message):
    if not condition:
        raise FixtureError(message)


def pointer(document, path):
    """Resolve the object-only JSON pointers used by this fixture."""
    require(isinstance(path, str) and path.startswith("/"), "invalid evidence pointer")
    value = document
    for token in path[1:].split("/"):
        token = token.replace("~1", "/").replace("~0", "~")
        require(isinstance(value, dict) and token in value, "unresolved pointer: " + path)
        value = value[token]
    return value


def strict_date(value):
    if not isinstance(value, str) or not re.fullmatch(r"[0-9]{4}-[0-9]{2}-[0-9]{2}", value):
        raise ValueError("date must be an ISO calendar date YYYY-MM-DD")
    try:
        return date.fromisoformat(value)
    except ValueError as exc:
        raise ValueError("invalid calendar date: " + value) from exc


def payroll_boundary(period_end):
    """Only period END is relevant, never payment/request/publication date.

    Even distant past/future inputs express only this historical predicate.
    Neither side certifies applicability of the quoted text in that period.
    """
    crosses = strict_date(period_end) > date(2024, 12, 31)
    return {"payroll_period_end": period_end,
            "historical_side": "after" if crosses else "before",
            "crosses_amendment_boundary": crosses}


def assessment_boundary(year):
    """Compare year with the stated FIRST assessment year, not current law."""
    if type(year) is not int or not 1 <= year <= 9999:
        raise ValueError("assessment year must be an integer from 1 through 9999")
    return {"assessment_calendar_year": year,
            "first_assessment_calendar_year": 2025,
            "is_first_assessment_calendar_year": year == 2025,
            "historical_side": "after" if year >= 2025 else "before",
            "crosses_amendment_boundary": year >= 2025}


def check_evidence(document, evidence, expected_pointer, absent=False):
    require(isinstance(evidence, dict), "evidence must be an object")
    require(evidence.get("text_pointer") == expected_pointer, "wrong evidence text pointer")
    text = pointer(document, evidence["text_pointer"])
    require(isinstance(text, str) and text, "empty evidence text")
    if absent:
        require(evidence.get("basis") == "entire_compared_text" and "quote" not in evidence,
                "absence must refer to the entire compared text, not a fabricated quote")
    else:
        quote = evidence.get("quote")
        require(isinstance(quote, str) and quote and quote in text, "evidence quote not in source text")


def validate(document):
    """Strict, version-pinned fixture validation, NOT a generic law schema.

    Semantic checks give useful errors. A canonical digest then rejects any
    other drift (including quotation, metadata, provenance and unknown fields).
    JSON whitespace/key ordering can change without changing the contract.
    """
    try:
        require(isinstance(document, dict), "fixture root must be an object")
        require(document.get("schema_version") == "1.0", "unsupported schema_version")
        require(document.get("comparison_id") == COMPARISON_ID, "wrong comparison_id")
        require(document["scope"]["historical_boundary_only"] is True, "historical-only guard missing")
        for name, text in document["texts"].items():
            require(text["language"] == "de", "statutory text must remain German")
            source = pointer(document, text["source_pointer"])
            require(source in document["sources"].values(), "text must point to an official source")
            require(source["url"].startswith("https://www.ris.bka.gv.at/"), "non-RIS provenance")
            require(re.fullmatch(r"[0-9a-f]{64}", source["sha256"]) is not None, "invalid source hash")
            require(bool(text["locator"]) and bool(source["locator"]), "missing source locator")
            require(isinstance(text["text"], str) and text["text"], "empty statutory text: " + name)
        fields = document["fields"]
        for key, expected in (("daily_amount_eur", 3), ("annual_day_cap", 100), ("cap_period", "calendar_year")):
            field = fields[key]
            require(field["classification"] == "unchanged", key + " must be unchanged")
            for side in ("before", "after"):
                require(type(field[side]) is type(expected) and field[side] == expected,
                        key + " must preserve both historical values")
        for key in ("designation", "day_definition_reference"):
            require(fields[key]["classification"] == "changed" and fields[key]["before"] != fields[key]["after"],
                    key + " must be changed")
        for key in ("reporting_sentence", "employment_sentence"):
            require(fields[key]["before"] == "not_express_in_compared_text",
                    key + ": old value must not imply no former duty or excluded groups")
            require(fields[key]["classification"] == "added_in_compared_text", "wrong textual addition classification")
        for key, field in fields.items():
            for side in ("before", "after"):
                absent = side == "before" and key in ("reporting_sentence", "employment_sentence")
                check_evidence(document, field["evidence"][side], "/texts/" + side + "/text", absent)
        application = document["application"]
        require(application["effective_on"]["value"] == "2025-01-01", "wrong effective date")
        require(type(application["first_assessment_calendar_year"]["value"]) is int and
                application["first_assessment_calendar_year"]["value"] == 2025, "wrong first assessment year")
        require(application["payroll_period_end"]["operator"] == "strictly_after" and
                application["payroll_period_end"]["value"] == "2024-12-31", "wrong payroll END boundary")
        for field in application.values():
            check_evidence(document, field["evidence"], "/texts/application/text")
        require(document["boundary_cases"] == [payroll_boundary("2024-12-31"), payroll_boundary("2025-01-01")],
                "historical boundary regression failed")
        canonical = json.dumps(document, ensure_ascii=False, sort_keys=True,
                               separators=(",", ":"), allow_nan=False).encode("utf-8")
        require(hashlib.sha256(canonical).hexdigest() == FIXTURE_SHA256,
                "fixture differs from pinned historical comparison (not a general-purpose schema)")
    except (KeyError, TypeError, AttributeError, RecursionError) as exc:
        raise FixtureError("malformed fixture structure: " + str(exc)) from exc
    return document


def unique_object(pairs):
    result = {}
    for key, value in pairs:
        require(key not in result, "duplicate JSON key: " + key)
        result[key] = value
    return result


def reject_constant(value):
    raise FixtureError("non-finite JSON number: " + value)


def load_fixture(path):
    try:
        with Path(path).open("rb") as handle:
            raw = handle.read(MAX_FIXTURE_BYTES + 1)
        require(len(raw) <= MAX_FIXTURE_BYTES, "fixture exceeds size limit")
        document = json.loads(raw.decode("utf-8"), object_pairs_hook=unique_object, parse_constant=reject_constant)
        return validate(document)
    except (OSError, UnicodeError, json.JSONDecodeError, RecursionError) as exc:
        raise FixtureError("cannot read fixture: " + str(exc)) from exc


def text_diff(document):
    # Wrap at word boundaries only for display; quotations in JSON stay complete.
    import textwrap
    return "\n".join(difflib.unified_diff(
        textwrap.wrap(document["texts"]["before"]["text"], width=88),
        textwrap.wrap(document["texts"]["after"]["text"], width=88),
        fromfile="before: EStG §26 Z9 lit.a as of 2024-12-31",
        tofile="after: Art.8 Z3 BGBl. I 110/2024 (promulgated replacement)", lineterm=""))


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--fixture", type=Path, default=Path(__file__).with_name("telework-2025.fixture.json"))
    parser.add_argument("--payroll-period-end", metavar="YYYY-MM-DD", help="historical predicate on period END only")
    parser.add_argument("--assessment-year", type=int, help="historical first-assessment-year predicate")
    parser.add_argument("--diff", action="store_true", help="print German statutory unified diff after JSON summary")
    args = parser.parse_args(argv)
    try:
        document = load_fixture(args.fixture)
        result = {"comparison_id": COMPARISON_ID, "verified": True,
                  "verification": "pinned_fixture_integrity_and_historical_regression_not_source_authentication",
                  "limitation": LIMITATION,
                  "fields": {key: {k: field[k] for k in ("classification", "before", "after")}
                             for key, field in document["fields"].items()},
                  "boundary_regression": [payroll_boundary("2024-12-31"), payroll_boundary("2025-01-01")]}
        if args.payroll_period_end is not None:
            result["payroll_boundary"] = payroll_boundary(args.payroll_period_end)
        if args.assessment_year is not None:
            result["assessment_boundary"] = assessment_boundary(args.assessment_year)
        print(json.dumps(result, ensure_ascii=False, indent=2))
        if args.diff:
            print(text_diff(document))
        return 0
    except (FixtureError, ValueError) as exc:
        print("verification failed: " + str(exc), file=sys.stderr)
        return 2


if __name__ == "__main__":
    sys.exit(main())
