from pathlib import Path
import json, copy
ROOT=Path(__file__).resolve().parents[1]
BASE=json.loads((ROOT/'fixture/baseline-state.json').read_text())

class ConsumerState:
    def __init__(self, stock=None, version=None, receipts=None):
        self.stock=BASE['consumer']['initial_stock'] if stock is None else stock
        self.version=BASE['consumer']['initial_version'] if version is None else version
        self.receipts={} if receipts is None else copy.deepcopy(receipts)
    def snapshot(self):
        return {'stock':self.stock,'version':self.version,'receipts':copy.deepcopy(self.receipts)}
    def process_atomic(self,event):
        key=event['event_key']
        if key in self.receipts:
            prior=self.receipts[key]
            fp={'business_entity':event['business_entity'],'version':event['version'],'stock_delta':event['stock_delta']}
            prior_fp={k:prior.get(k) for k in ('business_entity','version','stock_delta')}
            if fp != prior_fp:
                return {'classification':'IDEMPOTENCY_KEY_COLLISION_REJECTED','stock':self.stock,'version':self.version,'receipt_count':len(self.receipts),'receipt_state':prior['state'],'side_effect_applied':False,'delivery_id':event['delivery_id'],'collision':True}
            return {'classification':'DUPLICATE_SUPPRESSED','stock':self.stock,'version':self.version,'receipt_count':len(self.receipts),'receipt_state':prior['state'],'side_effect_applied':False,'delivery_id':event['delivery_id'],'collision':False}
        if int(event['version']) <= int(self.version):
            self.receipts[key]={'state':'REJECTED_STALE','version':event['version'],'delivery_id':event['delivery_id']}
            return {'classification':'STALE_EVENT_REJECTED','stock':self.stock,'version':self.version,'receipt_count':len(self.receipts),'receipt_state':'REJECTED_STALE','side_effect_applied':False,'delivery_id':event['delivery_id']}
        before=self.snapshot()
        try:
            new_stock=self.stock+int(event['stock_delta'])
            new_receipts=copy.deepcopy(self.receipts)
            new_receipts[key]={'state':'COMMITTED','version':event['version'],'delivery_id':event['delivery_id'],'business_entity':event['business_entity'],'stock_delta':event['stock_delta']}
            self.stock=new_stock; self.version=int(event['version']); self.receipts=new_receipts
        except Exception:
            self.stock=before['stock']; self.version=before['version']; self.receipts=before['receipts']; raise
        return {'classification':'RECEIPT_COMMITTED_EFFECT_APPLIED','stock':self.stock,'version':self.version,'receipt_count':len(self.receipts),'receipt_state':'COMMITTED','side_effect_applied':True,'delivery_id':event['delivery_id']}

def run_probe(case_id):
    first=BASE['first_event']; dup=BASE['duplicate_delivery']; stale=BASE['stale_event']
    if case_id=='baseline_first_delivery':
        s=ConsumerState(); o=s.process_atomic(first); return {'case_id':case_id,**o,'event_key':first['event_key'],'stable_key_survives_redelivery':True}
    if case_id=='duplicate_redelivery_new_transport_id':
        s=ConsumerState(); first_o=s.process_atomic(first); before=s.snapshot(); o=s.process_atomic(dup); after=s.snapshot(); return {'case_id':case_id,**o,'first_delivery_id':first['delivery_id'],'redelivery_id':dup['delivery_id'],'same_event_key':first['event_key']==dup['event_key'],'state_unchanged_after_duplicate':before==after}
    if case_id=='non_atomic_crash_window':
        stock=BASE['consumer']['initial_stock']; receipts={}; version=BASE['consumer']['initial_version']
        stock += int(first['stock_delta'])  # broken: effect visible
        crashed_before_receipt=True
        if first['event_key'] not in receipts: # redelivery cannot see a receipt
            stock += int(dup['stock_delta']); version=int(dup['version'])
        first_broken_stock=BASE['consumer']['initial_stock']+int(first['stock_delta'])
        return {'case_id':case_id,'classification':'NON_ATOMIC_DUPLICATE_RISK','stock_after_first_broken_attempt':first_broken_stock,'stock_after_redelivery':stock,'crashed_before_receipt':crashed_before_receipt,'receipt_count':len(receipts),'duplicate_effect_observed':stock==94,'version':version}
    if case_id=='stale_lower_version':
        s=ConsumerState(); s.process_atomic(first); before=s.snapshot(); o=s.process_atomic(stale); return {'case_id':case_id,**o,'accepted_current_version_before':before['version'],'incoming_version':stale['version'],'stock_unchanged':o['stock']==before['stock'],'distinct_event_key':stale['event_key']!=first['event_key']}
    if case_id=='evidence_field_removed':
        required={'Event Key','Business Entity','Version','Delivery Attempt','Receipt State','Side Effect','Duplicate Decision','Ordering Decision','Evidence','Residual Risk'}
        row={k:'x' for k in required}; row.pop('Residual Risk')
        missing=sorted(required-set(row))
        return {'case_id':case_id,'classification':'EVIDENCE_INCOMPLETE' if missing==['Residual Risk'] else 'UNEXPECTED','missing':missing}
    raise KeyError(case_id)

def publisher_selftest():
    cases=json.loads((ROOT/'fixture/probe-cases.json').read_text())['cases']; exp={c['id']:c['expected_class'] for c in cases}
    obs=[run_probe(c['id']) for c in cases]
    checks=[o['classification']==exp[o['case_id']] for o in obs]
    b=next(o for o in obs if o['case_id']=='baseline_first_delivery'); d=next(o for o in obs if o['case_id']=='duplicate_redelivery_new_transport_id'); n=next(o for o in obs if o['case_id']=='non_atomic_crash_window'); s=next(o for o in obs if o['case_id']=='stale_lower_version')
    first=BASE['first_event']; dup=BASE['duplicate_delivery']
    collision_entity=copy.deepcopy(dup); collision_entity['business_entity']='sku-999'; cs=ConsumerState(); cs.process_atomic(first); ce=cs.process_atomic(collision_entity)
    collision_version=copy.deepcopy(dup); collision_version['version']=11; vs=ConsumerState(); vs.process_atomic(first); cv=vs.process_atomic(collision_version)
    checks += [b['stock']==97,b['version']==10,b['receipt_count']==1,d['state_unchanged_after_duplicate'] is True,d['redelivery_id']!=d['first_delivery_id'],n['duplicate_effect_observed'] is True,n['stock_after_first_broken_attempt']==97,n['stock_after_redelivery']==94,s['stock_unchanged'] is True,s['incoming_version']==9,s['accepted_current_version_before']==10,s['distinct_event_key'] is True,ce['classification']=='IDEMPOTENCY_KEY_COLLISION_REJECTED',ce['side_effect_applied'] is False,cv['classification']=='IDEMPOTENCY_KEY_COLLISION_REJECTED',cv['side_effect_applied'] is False]
    return {'cases':obs,'checks':checks,'pass':all(checks),'runtime_scope':'PYTHON_IN_MEMORY_SYNTHETIC_FIXTURE_ONLY','broker_runtime_claimed':False,'production_runtime_claimed':False}
