from datetime import datetime
MISSING={None,"","MISSING","UNKNOWN","N/A"}
VALID_SUB={"ACTIVE","PAST_DUE","CANCELED","UNPAID"}
VALID_PAY={"SUCCEEDED","FAILED","PENDING"}
VALID_FAILURE={"NONE","RETRYABLE","NON_RETRYABLE"}
VALID_NOTICE={"NONE","FAILURE_NOTICE_SENT","DUNNING_SENT","ACTION_REQUIRED_SENT","RECOVERY_CONFIRMATION_SENT"}
VALID_ENT={"FULL","LIMITED","SUSPENDED","REVOKED"}
VALID_REC={"CURRENT","RETRY_SCHEDULED","RETRYING","CUSTOMER_ACTION_REQUIRED","RECOVERED","EXHAUSTED","CANCELED"}

def missing(v): return v in MISSING

def dt(v):
    if v in {None,"NONE","NOT_APPLICABLE"}: return None
    try: return datetime.fromisoformat(v)
    except Exception: return "INVALID"

def evaluate(x):
    required=["subscription_id","subscription_state","invoice_id","payment_state","failure_class","retry_eligible","attempt_count","next_attempt_at","retry_policy_ref","dunning_policy_ref","notification_state","grace_policy_ref","grace_started_at","grace_expires_at","entitlement_state","entitlement_reason","customer_action_required","recovery_state","policy_version","audit_ref"]
    for f in required:
        if missing(x.get(f)): return "HOLD_REQUIRED_EVIDENCE_MISSING:"+f
    if x.get("subscription_state") not in VALID_SUB: return "HOLD_SUBSCRIPTION_STATE_INVALID"
    if x.get("payment_state") not in VALID_PAY: return "HOLD_PAYMENT_STATE_INVALID"
    if x.get("failure_class") not in VALID_FAILURE: return "HOLD_FAILURE_CLASS_INVALID"
    if x.get("notification_state") not in VALID_NOTICE: return "HOLD_NOTIFICATION_STATE_INVALID"
    if x.get("entitlement_state") not in VALID_ENT: return "HOLD_ENTITLEMENT_STATE_INVALID"
    if x.get("recovery_state") not in VALID_REC: return "HOLD_RECOVERY_STATE_INVALID"
    if not isinstance(x.get("attempt_count"),int) or x["attempt_count"]<0: return "HOLD_ATTEMPT_COUNT_INVALID"
    if not isinstance(x.get("retry_budget"),int) or x["retry_budget"]<1: return "HOLD_RETRY_BUDGET_INVALID"
    if x.get("entitlement_authority")!="PRODUCT_POLICY": return "HOLD_PROVIDER_STATUS_ENTITLEMENT_SHORTCUT"
    if x.get("baseline_decision_reused") is True: return "HOLD_TRANSFER_BASELINE_ORACLE_REUSE"
    if x.get("transfer_variant") not in {None,"NONE","BASELINE"} and x.get("transfer_recomputed") is not True: return "HOLD_TRANSFER_NOT_RECOMPUTED"
    decision=dt(x.get("decision_time"))
    if decision in {None,"INVALID"}: return "HOLD_DECISION_TIME_INVALID"
    # Manual exception must be attributable and finite.
    if x.get("manual_override") is True:
        if x.get("override_ref") in MISSING|{"NONE"}: return "HOLD_OVERRIDE_REF_MISSING"
        if x.get("override_owner") in MISSING|{"NONE"}: return "HOLD_OVERRIDE_OWNER_MISSING"
        oe=dt(x.get("override_expires_at"))
        if oe in {None,"INVALID"}: return "HOLD_OVERRIDE_EXPIRY_INVALID"
        if oe<=decision: return "HOLD_OVERRIDE_EXPIRED"
    # Payment-success consistency.
    if x.get("payment_state")=="SUCCEEDED":
        if x.get("failure_class")!="NONE": return "HOLD_SUCCESS_FAILURE_CLASS_CONFLICT"
        if x.get("retry_eligible") is not False or x.get("next_attempt_at")!="NONE": return "HOLD_SUCCESS_RETRY_CONFLICT"
        if x.get("recovery_state") not in {"RECOVERED","CURRENT"}: return "HOLD_SUCCESS_RECOVERY_STATE_CONFLICT"
        if x.get("entitlement_state")!="FULL": return "HOLD_RECOVERED_ENTITLEMENT_CONFLICT"
        if x.get("duplicate_event_after_recovery") is True and x.get("notification_state") in {"FAILURE_NOTICE_SENT","DUNNING_SENT","ACTION_REQUIRED_SENT"}: return "HOLD_DUPLICATE_DUNNING_AFTER_RECOVERY"
        return "RECOVERED_REVIEW_READY"
    if x.get("payment_state")=="PENDING": return "HOLD_PAYMENT_PENDING"
    # Failed collection must be classified.
    if x.get("failure_class")=="NONE": return "HOLD_FAILED_PAYMENT_UNCLASSIFIED"
    fc=x.get("failure_class")
    if fc=="NON_RETRYABLE":
        if x.get("retry_eligible") is not False or x.get("next_attempt_at")!="NONE": return "HOLD_NONRETRYABLE_RETRY_CONFLICT"
        if x.get("customer_action_required") is not True: return "HOLD_NONRETRYABLE_CUSTOMER_ACTION_MISSING"
        if x.get("recovery_state") not in {"CUSTOMER_ACTION_REQUIRED","CANCELED","EXHAUSTED"}: return "HOLD_NONRETRYABLE_RECOVERY_STATE_INVALID"
    else: # RETRYABLE
        if x.get("attempt_count")>=x.get("retry_budget"):
            if x.get("retry_eligible") is True or x.get("recovery_state") in {"RETRYING","RETRY_SCHEDULED"}: return "HOLD_RETRY_BUDGET_EXCEEDED"
        else:
            if x.get("retry_eligible") is not True: return "HOLD_RETRYABLE_NOT_ELIGIBLE"
            na=dt(x.get("next_attempt_at"))
            if na in {None,"INVALID"}: return "HOLD_NEXT_ATTEMPT_INVALID"
            if na<=decision: return "HOLD_NEXT_ATTEMPT_NOT_FUTURE"
            if x.get("recovery_state") not in {"RETRY_SCHEDULED","RETRYING"}: return "HOLD_RETRYABLE_RECOVERY_STATE_INVALID"
    # Grace semantics.
    gp=x.get("grace_policy_ref")
    if gp=="NO_GRACE":
        if x.get("grace_started_at")!="NONE" or x.get("grace_expires_at")!="NONE": return "HOLD_NO_GRACE_TIMESTAMP_CONFLICT"
        if x.get("entitlement_state") in {"FULL","LIMITED"} and x.get("manual_override") is not True: return "HOLD_NO_GRACE_ENTITLEMENT_CONFLICT"
    else:
        gs=dt(x.get("grace_started_at")); ge=dt(x.get("grace_expires_at"))
        if gs in {None,"INVALID"} or ge in {None,"INVALID"}: return "HOLD_GRACE_WINDOW_INVALID"
        if gs>=ge: return "HOLD_GRACE_WINDOW_INVALID"
        if decision<=ge:
            if x.get("entitlement_state")!="LIMITED" and x.get("manual_override") is not True: return "HOLD_ACTIVE_GRACE_ENTITLEMENT_CONFLICT"
        else:
            if x.get("entitlement_state") in {"FULL","LIMITED"} and x.get("manual_override") is not True: return "HOLD_GRACE_EXPIRED_ENTITLEMENT_NOT_RECOMPUTED"
    # Subscription/access consistency.
    if x.get("subscription_state")=="CANCELED" and x.get("entitlement_state") not in {"SUSPENDED","REVOKED"}: return "HOLD_CANCELED_ENTITLEMENT_CONFLICT"
    if x.get("customer_action_required") is True and x.get("notification_state") not in {"ACTION_REQUIRED_SENT","DUNNING_SENT"}: return "HOLD_CUSTOMER_ACTION_NOTICE_MISSING"
    if x.get("duplicate_event_after_recovery") is True and x.get("recovery_state")=="RECOVERED" and x.get("notification_state") in {"FAILURE_NOTICE_SENT","DUNNING_SENT","ACTION_REQUIRED_SENT"}: return "HOLD_DUPLICATE_DUNNING_AFTER_RECOVERY"
    if fc=="NON_RETRYABLE": return "CUSTOMER_ACTION_REVIEW_READY"
    if x.get("attempt_count")>=x.get("retry_budget"): return "GRACE_EXIT_REVIEW_READY"
    return "RECOVERY_REVIEW_READY"
