import json, sys

def cmp(a,b):
    return isinstance(a,dict) and isinstance(b,dict) and a==b

def gate(packet):
    if not isinstance(packet,dict): return {'verdict':'REFUSED','code':'PACKET_NOT_OBJECT'}
    read=packet.get('read'); sent=packet.get('ack_sent'); ack=packet.get('ack_response')
    if read is None or sent is None:
        return {'verdict':'INAPPLICABLE','code':'NO_ACK_SUPPLIED'}
    offer=(read.get('ack_cursor') if isinstance(read,dict) else None)
    if not isinstance(offer,dict) or not isinstance(sent,dict):
        return {'verdict':'REFUSED','code':'ACK_SHAPE_INVALID'}
    if sent != offer:
        return {'verdict':'REFUSED','code':'ACK_NOT_EXACT'}
    if not isinstance(ack,dict):
        return {'verdict':'UNKNOWN','code':'ACK_RESPONSE_MISSING'}
    if ack.get('advanced') is True and ack.get('mode')=='lossless':
        return {'verdict':'PASS','code':'ACK_EXACT'}
    return {'verdict':'REFUSED','code':'ACK_RESPONSE_NOT_LOSSLESS'}

def batch_gate(reads,sent,acks):
    if not reads or not isinstance(sent,dict): return {'verdict':'INAPPLICABLE','code':'NO_ACK_SUPPLIED'}
    offers=[r.get('ack_cursor') for r in reads]
    if any(not isinstance(x,dict) for x in offers): return {'verdict':'REFUSED','code':'ACK_SHAPE_INVALID'}
    keys=set().union(*(x.keys() for x in offers))
    minimum={k:min(x.get(k) for x in offers) for k in keys}
    if sent != minimum: return {'verdict':'REFUSED','code':'BATCH_NOT_COMPONENTWISE_MIN'}
    if not isinstance(acks,dict) or acks.get('advanced') is not True or acks.get('mode')!='lossless': return {'verdict':'UNKNOWN','code':'ACK_RESPONSE_NOT_LOSSLESS'}
    return {'verdict':'PASS','code':'BATCH_MIN_EXACT'}

def main():
    def p(name, value, expected):
        got=gate(value); return {'name':name,'got':got,'expected':expected,'ok':got==expected}
    base={'read':{'ack_cursor':{'comments':10,'mentions':20}},'ack_sent':{'comments':10,'mentions':20},'ack_response':{'advanced':True,'mode':'lossless'}}
    rows=[p('positive',base,{'verdict':'PASS','code':'ACK_EXACT'}),p('missing_ack',{'read':base['read']},{'verdict':'INAPPLICABLE','code':'NO_ACK_SUPPLIED'}),p('over_offer',{**base,'ack_sent':{'comments':11,'mentions':20}},{'verdict':'REFUSED','code':'ACK_NOT_EXACT'}),p('lower_offer',{**base,'ack_sent':{'comments':9,'mentions':20}},{'verdict':'REFUSED','code':'ACK_NOT_EXACT'}),p('malformed',{**base,'ack_sent':{'comments':10}},{'verdict':'REFUSED','code':'ACK_NOT_EXACT'})]
    b=batch_gate([{'ack_cursor':{'comments':10,'mentions':20}},{'ack_cursor':{'comments':12,'mentions':18}}],{'comments':10,'mentions':18},{'advanced':True,'mode':'lossless'})
    rows.append({'name':'batch_minimum','got':b,'expected':{'verdict':'PASS','code':'BATCH_MIN_EXACT'},'ok':b=={'verdict':'PASS','code':'BATCH_MIN_EXACT'}})
    out={'contract':'raw-wire-ack-gate-v1','results':rows,'all_pass':all(x['ok'] for x in rows)}
    print(json.dumps(out,sort_keys=True,indent=2)); sys.exit(0 if out['all_pass'] else 1)
main()
