import json,pathlib,copy,sys
from evaluator import evaluate
root=pathlib.Path(__file__).parent
cases={x["case"]:x for x in json.loads((root/"fixtures/cases.json").read_text()) ["cases"]}
b=cases["baseline_monthly_retryable_grace"]

def m(**kw):
    x=copy.deepcopy(b); x.update(kw); return x

tests=[]
def t(name,obj,expected): tests.append((name,obj,expected))

t("baseline",b,"RECOVERY_REVIEW_READY")
t("nonretryable_conflict",cases["mutation_nonretryable_retry_conflict"],"HOLD_NONRETRYABLE_RETRY_CONFLICT")
t("transfer",cases["transfer_annual_no_grace_nonretryable"],"CUSTOMER_ACTION_REVIEW_READY")
t("provider_status_shortcut",m(entitlement_authority="PROVIDER_STATUS"),"HOLD_PROVIDER_STATUS_ENTITLEMENT_SHORTCUT")
t("attempt_negative",m(attempt_count=-1),"HOLD_ATTEMPT_COUNT_INVALID")
t("budget_invalid",m(retry_budget=0),"HOLD_RETRY_BUDGET_INVALID")
t("retryable_not_eligible",m(retry_eligible=False),"HOLD_RETRYABLE_NOT_ELIGIBLE")
t("retryable_next_none",m(next_attempt_at="NONE"),"HOLD_NEXT_ATTEMPT_INVALID")
t("retryable_next_past",m(next_attempt_at="2026-09-13T10:00:00"),"HOLD_NEXT_ATTEMPT_NOT_FUTURE")
t("budget_exceeded_retrying",m(attempt_count=3,retry_eligible=True,recovery_state="RETRYING"),"HOLD_RETRY_BUDGET_EXCEEDED")
# valid exhausted state after active grace expiry
ex=m(attempt_count=3,retry_eligible=False,next_attempt_at="NONE",recovery_state="EXHAUSTED",decision_time="2026-09-22T09:00:00",entitlement_state="SUSPENDED",entitlement_reason="GRACE_EXPIRED_RETRY_EXHAUSTED",customer_action_required=True,notification_state="ACTION_REQUIRED_SENT")
t("exhausted_valid",ex,"GRACE_EXIT_REVIEW_READY")
t("grace_expired_full",{**ex,"entitlement_state":"FULL","entitlement_reason":"STALE"},"HOLD_GRACE_EXPIRED_ENTITLEMENT_NOT_RECOMPUTED")
t("active_grace_full",m(entitlement_state="FULL"),"HOLD_ACTIVE_GRACE_ENTITLEMENT_CONFLICT")
t("grace_inverted",m(grace_started_at="2026-09-22T00:00:00",grace_expires_at="2026-09-21T00:00:00"),"HOLD_GRACE_WINDOW_INVALID")
t("grace_start_missing",m(grace_started_at="NONE"),"HOLD_GRACE_WINDOW_INVALID")
ng=copy.deepcopy(cases["transfer_annual_no_grace_nonretryable"])
t("no_grace_valid",ng,"CUSTOMER_ACTION_REVIEW_READY")
t("no_grace_timestamps",{**ng,"grace_started_at":"2026-09-14T08:00:00"},"HOLD_NO_GRACE_TIMESTAMP_CONFLICT")
t("no_grace_full",{**ng,"entitlement_state":"FULL"},"HOLD_NO_GRACE_ENTITLEMENT_CONFLICT")
t("nonretryable_customer_action_missing",{**ng,"customer_action_required":False},"HOLD_NONRETRYABLE_CUSTOMER_ACTION_MISSING")
t("nonretryable_recovery_invalid",{**ng,"recovery_state":"RETRYING"},"HOLD_NONRETRYABLE_RECOVERY_STATE_INVALID")
t("customer_action_notice_missing",{**ng,"notification_state":"NONE"},"HOLD_CUSTOMER_ACTION_NOTICE_MISSING")
t("transfer_oracle_reuse",{**ng,"baseline_decision_reused":True},"HOLD_TRANSFER_BASELINE_ORACLE_REUSE")
t("transfer_not_recomputed",{**ng,"transfer_recomputed":False},"HOLD_TRANSFER_NOT_RECOMPUTED")
# manual override checks
ov=m(manual_override=True,override_ref="OVR-1",override_owner="SUPPORT-LEAD",override_expires_at="2026-09-16T00:00:00",entitlement_state="FULL")
t("override_valid",ov,"RECOVERY_REVIEW_READY")
t("override_ref_missing",{**ov,"override_ref":"NONE"},"HOLD_OVERRIDE_REF_MISSING")
t("override_owner_missing",{**ov,"override_owner":"NONE"},"HOLD_OVERRIDE_OWNER_MISSING")
t("override_expired",{**ov,"override_expires_at":"2026-09-14T08:30:00"},"HOLD_OVERRIDE_EXPIRED")
# recovered states
rec=m(subscription_state="ACTIVE",payment_state="SUCCEEDED",failure_class="NONE",retry_eligible=False,next_attempt_at="NONE",notification_state="RECOVERY_CONFIRMATION_SENT",grace_policy_ref="NO_GRACE",grace_started_at="NONE",grace_expires_at="NONE",entitlement_state="FULL",entitlement_reason="PAYMENT_RECOVERED",customer_action_required=False,recovery_state="RECOVERED",attempt_count=2)
t("recovered",rec,"RECOVERED_REVIEW_READY")
t("recovered_failure_class",{**rec,"failure_class":"RETRYABLE"},"HOLD_SUCCESS_FAILURE_CLASS_CONFLICT")
t("recovered_retry_conflict",{**rec,"retry_eligible":True,"next_attempt_at":"2026-09-15T10:00:00"},"HOLD_SUCCESS_RETRY_CONFLICT")
t("recovered_state_conflict",{**rec,"recovery_state":"RETRYING"},"HOLD_SUCCESS_RECOVERY_STATE_CONFLICT")
t("recovered_entitlement_conflict",{**rec,"entitlement_state":"LIMITED"},"HOLD_RECOVERED_ENTITLEMENT_CONFLICT")
t("duplicate_after_recovery",{**rec,"duplicate_event_after_recovery":True,"notification_state":"DUNNING_SENT"},"HOLD_DUPLICATE_DUNNING_AFTER_RECOVERY")
# state/required checks
t("subscription_invalid",m(subscription_state="MAGIC"),"HOLD_SUBSCRIPTION_STATE_INVALID")
t("payment_invalid",m(payment_state="MAGIC"),"HOLD_PAYMENT_STATE_INVALID")
t("failure_invalid",m(failure_class="MAYBE"),"HOLD_FAILURE_CLASS_INVALID")
t("notice_invalid",m(notification_state="SPAM"),"HOLD_NOTIFICATION_STATE_INVALID")
t("entitlement_invalid",m(entitlement_state="FOREVER"),"HOLD_ENTITLEMENT_STATE_INVALID")
t("recovery_invalid",m(recovery_state="MAGIC"),"HOLD_RECOVERY_STATE_INVALID")
t("audit_missing",m(audit_ref="UNKNOWN"),"HOLD_REQUIRED_EVIDENCE_MISSING:audit_ref")
t("subscription_missing",m(subscription_id="UNKNOWN"),"HOLD_REQUIRED_EVIDENCE_MISSING:subscription_id")
t("decision_time_invalid",m(decision_time="yesterday"),"HOLD_DECISION_TIME_INVALID")
t("failed_unclassified",m(failure_class="NONE"),"HOLD_FAILED_PAYMENT_UNCLASSIFIED")
t("pending_payment",m(payment_state="PENDING"),"HOLD_PAYMENT_PENDING")
# canceled access conflict
c=copy.deepcopy(ng); c.update(subscription_state="CANCELED",entitlement_state="FULL",manual_override=True,override_ref="OVR-2",override_owner="SUPPORT-LEAD",override_expires_at="2026-09-16T00:00:00")
t("canceled_full",c,"HOLD_CANCELED_ENTITLEMENT_CONFLICT")

passed=0
for name,obj,expected in tests:
    got=evaluate(obj); ok=got==expected
    print(("PASS" if ok else "FAIL"),name,"=>",got)
    if not ok: print(" expected",expected)
    passed+=int(ok)
print(f"RESULT {passed}/{len(tests)} PASS")
sys.exit(0 if passed==len(tests) else 1)
