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

class TransitionRejected(Exception): pass

class ReviewMachine:
    def __init__(self, base=None):
        b=base or BASE
        self.state=b['initial_state']; self.states=list(b['states']); self.terminal=set(b['terminal_states']); self.transitions=[dict(x) for x in b['transitions']]; self.trace=[]
    def _edge(self, trigger):
        for e in self.transitions:
            if e['from']==self.state and e['trigger']==trigger: return e
        return None
    def transition(self, trigger, context=None):
        context=context or {}; current=self.state
        if current in self.terminal:
            result={'accepted':False,'classification':'ILLEGAL_TRANSITION_REJECTED','from':current,'trigger':trigger,'to':None,'reason':'CURRENT_STATE_TERMINAL'}; self.trace.append(result); return result
        edge=self._edge(trigger)
        if not edge:
            result={'accepted':False,'classification':'ILLEGAL_TRANSITION_REJECTED','from':current,'trigger':trigger,'to':None,'reason':'NO_DECLARED_EDGE'}; self.trace.append(result); return result
        guard=edge['guard']; ok=bool(context.get(guard,False))
        if not ok:
            result={'accepted':False,'classification':'GUARD_REJECTED','from':current,'trigger':trigger,'to':None,'guard':guard,'guard_value':False,'state_after':self.state}; self.trace.append(result); return result
        self.state=edge['to']; result={'accepted':True,'classification':'TRANSITION_ACCEPTED','from':current,'trigger':trigger,'to':self.state,'guard':guard,'guard_value':True,'terminal_after':self.state in self.terminal}; self.trace.append(result); return result
    def graph_contract(self):
        outgoing={s:[] for s in self.states}
        for e in self.transitions: outgoing.setdefault(e['from'],[]).append(e)
        violations=[s for s in self.terminal if outgoing.get(s)]
        return {'terminal_outgoing_violations':sorted(violations),'valid':not violations}

def run_probe(case_id):
    if case_id=='baseline_bounded_review_flow':
        m=ReviewMachine(); steps=[m.transition('submit',{'has_required_fields':True}),m.transition('hold',{'needs_more_info':True}),m.transition('resume',{'resume_ready':True}),m.transition('approve',{'approver_authorized':True})]
        return {'case_id':case_id,'classification':'BOUNDED_FLOW_COMPLETED' if m.state=='APPROVED' and all(x['accepted'] for x in steps) else 'UNEXPECTED','final_state':m.state,'trace':steps,'terminal':m.state in m.terminal}
    if case_id=='guard_bypass_attempt':
        m=ReviewMachine(); m.transition('submit',{'has_required_fields':True}); r=m.transition('approve',{'approver_authorized':False})
        return {'case_id':case_id,'classification':'GUARD_REJECTED' if (not r['accepted'] and m.state=='SUBMITTED') else 'UNEXPECTED','state_after':m.state,'result':r}
    if case_id=='hold_marked_terminal_mutation':
        b=json.loads(json.dumps(BASE)); b['terminal_states'].append('ON_HOLD'); m=ReviewMachine(b); m.transition('submit',{'has_required_fields':True}); m.transition('hold',{'needs_more_info':True}); r=m.transition('resume',{'resume_ready':True})
        return {'case_id':case_id,'classification':'RESUME_PATH_BLOCKED_BY_FALSE_TERMINAL' if (m.state=='ON_HOLD' and not r['accepted'] and r['reason']=='CURRENT_STATE_TERMINAL') else 'UNEXPECTED','state_after':m.state,'resume_result':r}
    if case_id=='terminal_outgoing_transition_mutation':
        b=json.loads(json.dumps(BASE)); b['transitions'].append({'from':'APPROVED','trigger':'reopen','to':'DRAFT','guard':'reopen_allowed'}); m=ReviewMachine(b); gc=m.graph_contract()
        return {'case_id':case_id,'classification':'TERMINAL_CONTRACT_VIOLATION' if gc['terminal_outgoing_violations']==['APPROVED'] else 'UNEXPECTED','contract':gc}
    if case_id=='approved_to_draft_attempt':
        m=ReviewMachine(); m.transition('submit',{'has_required_fields':True}); m.transition('approve',{'approver_authorized':True}); r=m.transition('reopen',{'reopen_allowed':True})
        return {'case_id':case_id,'classification':'ILLEGAL_TRANSITION_REJECTED' if (m.state=='APPROVED' and not r['accepted']) else 'UNEXPECTED','state_after':m.state,'result':r}
    if case_id=='evidence_field_removed':
        required=set(BASE['record_fields']); row={k:'observed' for k in required}; row.pop('Recovery or Resume Rule'); missing=sorted(required-set(row))
        return {'case_id':case_id,'classification':'EVIDENCE_INCOMPLETE' if missing==['Recovery or Resume Rule'] else 'UNEXPECTED','missing':missing}
    raise KeyError(case_id)

def publisher_selftest():
    cases=json.loads((ROOT/'fixture/probe-cases.json').read_text())['cases']; expected={c['id']:c['expected_class'] for c in cases}; obs=[run_probe(c['id']) for c in cases]
    checks=[o['classification']==expected[o['case_id']] for o in obs]
    b=next(o for o in obs if o['case_id']=='baseline_bounded_review_flow'); g=next(o for o in obs if o['case_id']=='guard_bypass_attempt'); h=next(o for o in obs if o['case_id']=='hold_marked_terminal_mutation'); t=next(o for o in obs if o['case_id']=='terminal_outgoing_transition_mutation'); i=next(o for o in obs if o['case_id']=='approved_to_draft_attempt'); e=next(o for o in obs if o['case_id']=='evidence_field_removed')
    checks += [b['final_state']=='APPROVED',b['terminal'] is True,len(b['trace'])==4,g['state_after']=='SUBMITTED',g['result']['guard']=='approver_authorized',h['state_after']=='ON_HOLD',h['resume_result']['reason']=='CURRENT_STATE_TERMINAL',t['contract']['terminal_outgoing_violations']==['APPROVED'],i['state_after']=='APPROVED',e['missing']==['Recovery or Resume Rule']]
    return {'cases':obs,'checks':checks,'pass':all(checks),'runtime_scope':'PYTHON_IN_MEMORY_SYNTHETIC_FIXTURE_ONLY','workflow_engine_runtime_claimed':False,'production_runtime_claimed':False}
