from datetime import datetime
MISSING={None,"","MISSING","UNKNOWN","N/A"}
REQ=['exit_case_id','asset_inventory_ref','data_inventory_ref','agent_memory_inventory_ref','export_scope','export_format','export_integrity_hash','export_delivery_state','deletion_scope','retention_exception_ref','retention_until','primary_delete_state','backup_delete_state','subprocessor_delete_state','credential_revoke_state','integration_disconnect_state','residual_scan_ref','residual_state','deletion_certificate_ref','legal_review_required','closure_state','audit_ref']
AUX=['export_required','export_basis','retained_data_present','retention_exception_valid','return_required','return_state','backup_expiry_evidence_ref','subprocessor_evidence_ref','active_credential_count','active_integration_count','residual_items_count','certificate_evidence_bound','legal_review_state','closure_recomputed','transfer_variant','transfer_recomputed','baseline_decision_reused','baseline_basis_reused','contractual_export_promise','portability_right_applies']
def missing(v): return v in MISSING
def date(v):
    try: return datetime.fromisoformat(v)
    except: return None
def evaluate(x):
    for f in REQ:
        if missing(x.get(f)): return 'HOLD_REQUIRED_EVIDENCE_MISSING:'+f
    for f in AUX:
        if f in {'retention_exception_valid','return_required','certificate_evidence_bound','closure_recomputed','transfer_recomputed','baseline_decision_reused','baseline_basis_reused','contractual_export_promise','portability_right_applies'}:
            if f not in x: return 'HOLD_AUX_EVIDENCE_MISSING:'+f
        elif missing(x.get(f)): return 'HOLD_AUX_EVIDENCE_MISSING:'+f
    if x['export_required']:
        if missing(x.get('export_scope')) or missing(x.get('export_format')): return 'HOLD_EXPORT_SCOPE_OR_FORMAT_MISSING'
        if not str(x.get('export_integrity_hash','')).startswith('sha256:'): return 'HOLD_EXPORT_INTEGRITY_MISSING'
        if x.get('export_delivery_state')!='VERIFIED': return 'HOLD_EXPORT_DELIVERY_UNVERIFIED'
    if x.get('retained_data_present'):
        if x.get('retention_exception_ref') in {'NONE','NO_EXCEPTION','UNKNOWN'} or x.get('retention_exception_valid') is not True:
            return 'HOLD_RETENTION_BASIS_MISSING'
        if date(x.get('retention_until')) is None: return 'HOLD_RETENTION_UNTIL_INVALID'
    else:
        if x.get('retention_exception_ref') not in {'NONE','NO_EXCEPTION'} and x.get('retention_exception_valid') is True:
            return 'HOLD_FALSE_RETENTION_EXCEPTION'
    if x.get('primary_delete_state')!='DELETED': return 'HOLD_PRIMARY_DELETE_INCOMPLETE'
    if x.get('backup_delete_state') not in {'DELETED_OR_EXPIRED','SCHEDULED_FOR_EXPIRY'}: return 'HOLD_BACKUP_DELETE_STATE_INVALID'
    if x.get('backup_delete_state')=='SCHEDULED_FOR_EXPIRY' and missing(x.get('backup_expiry_evidence_ref')): return 'HOLD_BACKUP_EXPIRY_EVIDENCE_MISSING'
    if x.get('subprocessor_delete_state') not in {'DELETED_CONFIRMED','PENDING','RETURNED_CONFIRMED'}: return 'HOLD_SUBPROCESSOR_STATE_INVALID'
    if x.get('subprocessor_delete_state')=='PENDING' and missing(x.get('subprocessor_evidence_ref')): return 'HOLD_SUBPROCESSOR_EVIDENCE_MISSING'
    if x.get('return_required'):
        if x.get('return_state')!='RETURNED_CONFIRMED': return 'HOLD_RETURN_REQUIRED_NOT_COMPLETED'
    if x.get('active_credential_count',0)>0 and x.get('credential_revoke_state')!='ACTIVE': return 'HOLD_CREDENTIAL_COUNT_STATE_CONFLICT'
    if x.get('active_credential_count',0)==0 and x.get('credential_revoke_state')!='REVOKED': return 'HOLD_CREDENTIAL_REVOKE_INCOMPLETE'
    if x.get('active_integration_count',0)>0 and x.get('integration_disconnect_state')!='CONNECTED': return 'HOLD_INTEGRATION_COUNT_STATE_CONFLICT'
    if x.get('active_integration_count',0)==0 and x.get('integration_disconnect_state')!='DISCONNECTED': return 'HOLD_INTEGRATION_DISCONNECT_INCOMPLETE'
    if x.get('residual_state')=='CLEAR':
        if x.get('residual_items_count')!=0: return 'HOLD_RESIDUAL_CLEAR_COUNT_CONFLICT'
        if x.get('active_credential_count',0)>0: return 'HOLD_ACTIVE_CREDENTIAL_RESIDUAL_CONFLICT'
        if x.get('active_integration_count',0)>0: return 'HOLD_ACTIVE_INTEGRATION_RESIDUAL_CONFLICT'
        if x.get('subprocessor_delete_state')=='PENDING': return 'HOLD_PENDING_SUBPROCESSOR_RESIDUAL_CONFLICT'
    elif x.get('residual_state')=='KNOWN_RESIDUALS':
        if x.get('residual_items_count',0)<=0: return 'HOLD_RESIDUAL_COUNT_CONFLICT'
    else: return 'HOLD_RESIDUAL_STATE_INVALID'
    if x.get('certificate_evidence_bound') is not True: return 'HOLD_CERTIFICATE_NOT_EVIDENCE_BOUND'
    if x.get('legal_review_required') is True and x.get('legal_review_state') not in {'REQUIRED_OPEN','APPROVED_EXCEPTION'}: return 'HOLD_LEGAL_REVIEW_STATE_INVALID'
    if x.get('closure_state') not in {'PARTIAL','COMPLETE','HOLD'}: return 'HOLD_CLOSURE_STATE_INVALID'
    # false-complete gates are deliberately after individual evidence checks
    if x.get('closure_state')=='COMPLETE':
        if x.get('subprocessor_delete_state')=='PENDING': return 'HOLD_FALSE_COMPLETE_PENDING_SUBPROCESSOR'
        if x.get('backup_delete_state')=='SCHEDULED_FOR_EXPIRY': return 'HOLD_FALSE_COMPLETE_PENDING_BACKUP'
        if x.get('active_credential_count',0)>0 or x.get('credential_revoke_state')!='REVOKED': return 'HOLD_FALSE_COMPLETE_ACTIVE_CREDENTIAL'
        if x.get('active_integration_count',0)>0 or x.get('integration_disconnect_state')!='DISCONNECTED': return 'HOLD_FALSE_COMPLETE_ACTIVE_INTEGRATION'
        if x.get('residual_state')!='CLEAR': return 'HOLD_FALSE_COMPLETE_RESIDUALS'
        if x.get('legal_review_required') is True and x.get('legal_review_state')!='APPROVED_EXCEPTION': return 'HOLD_FALSE_COMPLETE_LEGAL_REVIEW_OPEN'
    if x.get('transfer_variant') not in {'BASELINE','NONE',None}:
        if x.get('transfer_recomputed') is not True: return 'HOLD_TRANSFER_NOT_RECOMPUTED'
        if x.get('baseline_decision_reused') is True: return 'HOLD_TRANSFER_BASELINE_ORACLE_REUSE'
        if x.get('baseline_basis_reused') is True: return 'HOLD_TRANSFER_BASELINE_BASIS_REUSE'
        if x.get('portability_right_applies') is False and x.get('export_required') and not x.get('contractual_export_promise'):
            return 'HOLD_TRANSFER_EXPORT_BASIS_UNSUPPORTED'
        if x.get('return_required') and x.get('return_state')!='RETURNED_CONFIRMED': return 'HOLD_RETURN_REQUIRED_NOT_COMPLETED'
        if x.get('closure_state')=='COMPLETE': return 'TRANSFER_EXIT_REVIEW_READY'
    if x.get('closure_state')=='COMPLETE': return 'EXIT_CLOSURE_COMPLETE_REVIEW_READY'
    if x.get('closure_state')=='PARTIAL': return 'EXIT_PARTIAL_REVIEW_READY'
    return 'EXIT_HOLD_REVIEW_READY'
