#!/usr/bin/env python3
"""Offline validation for the article's four-choice route response.

No network calls, model loading, or file writes. This checks format and numeric
consistency only; it cannot evaluate the meaning of a customer's message.
"""
import argparse
import json
import math
from pathlib import Path
import sys

LABELS = {"1", "2", "3", "4"}


def finite_probability(value, field):
    if isinstance(value, bool) or not isinstance(value, (int, float)):
        raise ValueError(f"{field}: expected a number")
    if not math.isfinite(value) or not 0 <= value <= 1:
        raise ValueError(f"{field}: expected a finite value from 0 to 1")
    return float(value)


def validate_response(response):
    if not isinstance(response, dict):
        raise ValueError("response: expected an object")
    answers = response.get("answers")
    if not isinstance(answers, dict) or set(answers) != {"route"}:
        raise ValueError("answers: expected the route question only")
    route = answers["route"]
    if not isinstance(route, dict) or route.get("type") != "choice":
        raise ValueError("route: expected a choice answer")
    probabilities = route.get("probabilities")
    if not isinstance(probabilities, dict) or set(probabilities) != LABELS:
        raise ValueError("probabilities: expected exactly labels 1, 2, 3, 4")
    values = {key: finite_probability(value, key)
              for key, value in probabilities.items()}
    if not math.isclose(sum(values.values()), 1.0, abs_tol=1e-6):
        raise ValueError("probabilities: sum must be approximately 1")
    choice = route.get("choice")
    if not isinstance(choice, str) or choice not in LABELS:
        raise ValueError("choice: unknown label")
    maximum = max(values.values())
    if not math.isclose(values[choice], maximum, abs_tol=1e-6):
        raise ValueError("choice: does not have a maximum probability")
    confidence = finite_probability(route.get("confidence"), "confidence")
    # Jeff v1.1's choice calculation; this is NOT P(the answer is correct).
    expected = max(0.0, min(1.0, (maximum - 1 / 4) / (1 - 1 / 4)))
    if not math.isclose(confidence, expected, abs_tol=1e-6):
        raise ValueError("confidence: does not match Jeff v1.1 choice semantics")
    return {"suggested_route": choice, "human_review_required": True}


def reject_nonfinite(token):
    raise ValueError(f"invalid JSON numeric constant: {token}")


def load_json(path):
    # Python's default decoder accepts NaN; real JSON does not.
    return json.loads(path.read_text(encoding="utf-8"),
                      parse_constant=reject_nonfinite)


def run_fixtures():
    path = Path(__file__).with_name("response-fixtures.json")
    fixtures = load_json(path)
    if not isinstance(fixtures, list) or not fixtures:
        raise ValueError("fixtures: expected a nonempty array")
    failures = 0
    for fixture in fixtures:
        try:
            response = fixture["response"]
            # JSON itself cannot contain Infinity; create it only in this
            # explicitly synthetic test to exercise the numeric validator.
            if fixture.get("inject_nonfinite"):
                response["answers"]["route"]["probabilities"]["1"] = float("inf")
            validate_response(response)
            accepted = True
        except ValueError:
            accepted = False
        expected = fixture["should_accept"]
        if not isinstance(expected, bool):
            raise ValueError("fixture should_accept must be boolean")
        matches = accepted == expected
        failures += not matches
        print(f"{'PASS' if matches else 'FAIL'} {fixture['id']}: "
              f"{'accepted' if accepted else 'rejected'}")
    if failures:
        raise ValueError(f"{failures} fixture checks failed")
    print(f"{len(fixtures)} fixture checks passed. No model was called.")


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--response", type=Path,
                        help="inspect one previously saved local JSON response")
    args = parser.parse_args()
    try:
        if args.response is None:
            run_fixtures()
        else:
            result = validate_response(load_json(args.response))
            print(json.dumps(result, ensure_ascii=False))
            print("Format/numeric checks passed. Classification quality is untested.")
    except (OSError, ValueError, KeyError, TypeError, OverflowError) as error:
        print(f"Validation failed: {error}", file=sys.stderr)
        return 1
    return 0


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