"""Standard-library reference for the article's aligned-checkpoint stack map.
Run: python verify.py (with cases.json in this script's directory).
This does not select poker actions, settle pots or inspect app output.
"""
from pathlib import Path
from itertools import combinations, permutations, product
import copy
import json
import math

def number(value):
    return not isinstance(value,bool) and isinstance(value,(int,float)) and math.isfinite(value) and value>=0

def stack_map(players):
    if not 2<=len(players)<=9:
        raise ValueError('Enter two to nine seats.')
    names=[p['seat'].strip() for p in players]
    if any(not n or '|' in n for n in names) or len({n.lower() for n in names})!=len(names):
        raise ValueError('Use unique, nonempty seat labels.')
    for p in players:
        if not number(p['behind_bb']) or not number(p['contribution_bb']):
            raise ValueError('Stacks and contributions must be finite nonnegative numbers.')
        if p['contribution_bb']!=0:
            raise ValueError('Map only at street start, before any current-street contribution.')
        if p['status'] not in ('active','folded','all-in'):
            raise ValueError('Unknown player status.')
        if p['status']=='all-in' and p['behind_bb']!=0:
            raise ValueError('All-in status requires zero chips behind.')
    active=[(p['seat'].strip(),p['behind_bb']) for p in players if p['status']=='active' and p['behind_bb']>0]
    pairs={'|'.join(sorted([a,b])):min(sa,sb) for (a,sa),(b,sb) in combinations(active,2)}
    matched={a:min(sa,max((sb for b,sb in active if b!=a),default=0)) for a,sa in active}
    return pairs,matched

def require(test,message):
    if not test:
        raise RuntimeError(message)

def main():
    data=json.loads(Path(__file__).with_name('cases.json').read_text(encoding='utf-8'))
    checks=0
    for c in data['cases']:
        expected=(c['expected_pairs'],c['expected_max_matched'])
        require(stack_map(c['players'])==expected,c['id']);checks+=1
        for perm in permutations(c['players']):
            require(stack_map(perm)==expected,'Input-order invariance');checks+=1
        for factor in (0.5,2,10):
            scaled=copy.deepcopy(c['players'])
            for p in scaled:p['behind_bb']*=factor
            got=stack_map(scaled)
            require(got==tuple({k:v*factor for k,v in e.items()} for e in expected),'Unit scaling');checks+=1
    # Separate capacity construction: count integer chip layers shared by both players.
    for sizes in product(range(1,9),repeat=3):
        ps=[dict(seat=chr(65+i),behind_bb=s,contribution_bb=0,status='active') for i,s in enumerate(sizes)]
        pairs,matched=stack_map(ps)
        layers={p['seat']:set(range(1,p['behind_bb']+1)) for p in ps}
        for a,b in combinations(layers,2):
            require(pairs[a+'|'+b]==len(layers[a]&layers[b]),'Shared layers');checks+=1
        for a in layers:
            others=set().union(*(layers[b] for b in layers if b!=a))
            require(matched[a]==len(layers[a]&others),'Matched union, not pair sum');checks+=1
    base=data['cases'][0]['players']
    invalid=[]
    for key,value in [('behind_bb',-1),('behind_bb',float('nan')),('behind_bb',float('inf')),('behind_bb',True),('contribution_bb',30),('contribution_bb',-1),('status','unknown'),('status','all-in'),('seat','')]:
        p=copy.deepcopy(base);p[0][key]=value;invalid.append(p)
    for name in ('A','a','B|C'):
        p=copy.deepcopy(base);p[1]['seat']=name;invalid.append(p)
    invalid.extend([base[:1],base*4])
    for p in invalid:
        try:stack_map(p)
        except ValueError:checks+=1
        else:raise RuntimeError('Invalid input accepted')
    m=data['midstreet']
    require(m['A_start_bb']-m['A_bet_bb']==m['A_behind_bb'],'A remaining');checks+=1
    require(m['B_shove_to_bb']-m['A_bet_bb']==m['A_additional_call_bb']==90,'Incremental call');checks+=1
    require(m['B_call_price_bb']==m['A_bet_bb']==30,'Initial call');checks+=1
    require(m['B_shove_to_bb']==m['B_start_bb']==120,'B total');checks+=1
    print(f'PASS: {checks} exact fixture, capacity, invariance and invalid-input checks.')

if __name__=='__main__':
    main()
