import json,pathlib,copy,sys
from evaluator import evaluate
root=pathlib.Path(__file__).parent
cs={x['case']:x for x in json.loads((root/'fixtures/cases.json').read_text())['cases']}
b=cs['baseline_partial']; tf=cs['transfer_return_contractual_export']
def m(base=b,**kw):
 x=copy.deepcopy(base); x.update(kw); return x
tests=[]
def t(n,o,e): tests.append((n,o,e))
t('baseline',b,'EXIT_PARTIAL_REVIEW_READY')
t('false_complete',cs['mutation_false_complete'],'HOLD_FALSE_COMPLETE_PENDING_SUBPROCESSOR')
t('transfer',tf,'TRANSFER_EXIT_REVIEW_READY')
# missing required
for f in ['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','residual_scan_ref','deletion_certificate_ref','audit_ref']:
 t('missing_'+f,m(**{f:'UNKNOWN'}),'HOLD_REQUIRED_EVIDENCE_MISSING:'+f)
# export
t('hash_bad',m(export_integrity_hash='md5:bad'),'HOLD_EXPORT_INTEGRITY_MISSING')
t('delivery_unverified',m(export_delivery_state='SENT'),'HOLD_EXPORT_DELIVERY_UNVERIFIED')
# retention
t('retention_basis_removed',m(retention_exception_ref='NONE'),'HOLD_RETENTION_BASIS_MISSING')
t('retention_invalid',m(retention_exception_valid=False),'HOLD_RETENTION_BASIS_MISSING')
t('retention_date_bad',m(retention_until='later'),'HOLD_RETENTION_UNTIL_INVALID')
no_ret=m(tf,retention_exception_ref='RET-X',retention_exception_valid=True)
t('false_retention',no_ret,'HOLD_FALSE_RETENTION_EXCEPTION')
# deletion/backup/subprocessor
t('primary_pending',m(primary_delete_state='PENDING'),'HOLD_PRIMARY_DELETE_INCOMPLETE')
t('backup_invalid',m(backup_delete_state='UNKNOWN_STATE'),'HOLD_BACKUP_DELETE_STATE_INVALID')
t('backup_evidence_missing',m(backup_expiry_evidence_ref='UNKNOWN'),'HOLD_AUX_EVIDENCE_MISSING:backup_expiry_evidence_ref')
t('subproc_invalid',m(subprocessor_delete_state='MAGIC'),'HOLD_SUBPROCESSOR_STATE_INVALID')
t('subproc_evidence_missing',m(subprocessor_evidence_ref='UNKNOWN'),'HOLD_AUX_EVIDENCE_MISSING:subprocessor_evidence_ref')
# credential/integration
t('cred_count_state_conflict',m(credential_revoke_state='REVOKED'),'HOLD_CREDENTIAL_COUNT_STATE_CONFLICT')
clean=copy.deepcopy(tf)
t('cred_not_revoked',m(clean,credential_revoke_state='ACTIVE'),'HOLD_CREDENTIAL_REVOKE_INCOMPLETE')
t('int_not_disconnected',m(clean,integration_disconnect_state='CONNECTED'),'HOLD_INTEGRATION_DISCONNECT_INCOMPLETE')
t('active_cred_residual_clear',m(clean,active_credential_count=1,credential_revoke_state='ACTIVE'),'HOLD_ACTIVE_CREDENTIAL_RESIDUAL_CONFLICT')
t('active_int_residual_clear',m(clean,active_integration_count=1,integration_disconnect_state='CONNECTED'),'HOLD_ACTIVE_INTEGRATION_RESIDUAL_CONFLICT')
# residual/cert/legal
t('residual_clear_count',m(clean,residual_items_count=1),'HOLD_RESIDUAL_CLEAR_COUNT_CONFLICT')
t('known_residual_zero',m(residual_items_count=0),'HOLD_RESIDUAL_COUNT_CONFLICT')
t('residual_invalid',m(residual_state='MAYBE'),'HOLD_RESIDUAL_STATE_INVALID')
t('certificate_unbound',m(certificate_evidence_bound=False),'HOLD_CERTIFICATE_NOT_EVIDENCE_BOUND')
t('legal_state_invalid',m(legal_review_state='UNKNOWN'),'HOLD_AUX_EVIDENCE_MISSING:legal_review_state')
# explicit false completes from otherwise isolated fixtures
x=copy.deepcopy(clean); x.update(subprocessor_delete_state='PENDING',subprocessor_evidence_ref='DSP-X',residual_state='KNOWN_RESIDUALS',residual_items_count=1,closure_state='COMPLETE')
t('complete_pending_subproc',x,'HOLD_FALSE_COMPLETE_PENDING_SUBPROCESSOR')
x=copy.deepcopy(clean); x.update(backup_delete_state='SCHEDULED_FOR_EXPIRY',closure_state='COMPLETE')
t('complete_pending_backup',x,'HOLD_FALSE_COMPLETE_PENDING_BACKUP')
x=copy.deepcopy(clean); x.update(active_credential_count=1,credential_revoke_state='ACTIVE',residual_state='KNOWN_RESIDUALS',residual_items_count=1,closure_state='COMPLETE')
t('complete_active_credential',x,'HOLD_FALSE_COMPLETE_ACTIVE_CREDENTIAL')
x=copy.deepcopy(clean); x.update(active_integration_count=1,integration_disconnect_state='CONNECTED',residual_state='KNOWN_RESIDUALS',residual_items_count=1,closure_state='COMPLETE')
t('complete_active_integration',x,'HOLD_FALSE_COMPLETE_ACTIVE_INTEGRATION')
x=copy.deepcopy(clean); x.update(residual_state='KNOWN_RESIDUALS',residual_items_count=1,closure_state='COMPLETE')
t('complete_residuals',x,'HOLD_FALSE_COMPLETE_RESIDUALS')
# transfer
t('transfer_not_recomputed',m(tf,transfer_recomputed=False),'HOLD_TRANSFER_NOT_RECOMPUTED')
t('transfer_oracle',m(tf,baseline_decision_reused=True),'HOLD_TRANSFER_BASELINE_ORACLE_REUSE')
t('transfer_basis_reuse',m(tf,baseline_basis_reused=True),'HOLD_TRANSFER_BASELINE_BASIS_REUSE')
t('transfer_no_export_basis',m(tf,contractual_export_promise=False),'HOLD_TRANSFER_EXPORT_BASIS_UNSUPPORTED')
t('transfer_return_pending',m(tf,return_state='PENDING'),'HOLD_RETURN_REQUIRED_NOT_COMPLETED')
# positive complete baseline variant
base_complete=copy.deepcopy(clean); base_complete.update(transfer_variant='BASELINE',portability_right_applies=True,export_basis='PORTABILITY_OR_CONTRACT')
t('complete_baseline',base_complete,'EXIT_CLOSURE_COMPLETE_REVIEW_READY')
passed=0
for n,o,e in tests:
 g=evaluate(o); ok=g==e; print(('PASS' if ok else 'FAIL'),n,'=>',g)
 if not ok: print(' expected',e)
 passed+=ok
print(f'RESULT {passed}/{len(tests)} PASS')
sys.exit(0 if passed==len(tests) else 1)
