import argparse,copy,hashlib,json,re
from pathlib import Path

parser=argparse.ArgumentParser();parser.add_argument('--native-date',action='store_true');parser.add_argument('--suffix',default='');args=parser.parse_args();private=Path(__file__).resolve().parent/'private-evidence';checks=[]
def check(ok,label):checks.append({'label':label,'passed':bool(ok)})
def equal(a,b):
    if type(a) is not type(b):return False
    if isinstance(a,dict):return a.keys()==b.keys() and all(equal(a[k],b[k]) for k in a)
    if isinstance(a,list):return len(a)==len(b) and all(equal(x,y) for x,y in zip(a,b))
    return a==b
def typed(raw):
    row=copy.deepcopy(raw)
    for k in ['id','train_status','status','del_flag','num','interval_time','version','order_no','number','train_time','use_number','employ']:
        if k in row and row[k] is not None:row[k]=int(row[k])
    for k in ['power','carbohydrate','protein','fat','sodium','use_weight']:
        if k in row and row[k] is not None:
            value=float(row[k]);row[k]=int(value) if value.is_integer() else value
    if not args.native_date:
        for k in ['create_time','date']:
            if isinstance(row.get(k),str):row[k]=re.sub(r'\.0{1,6}$','',row[k])
    return row
def media(v):return 'https://nutrition-media.invalid/'+v.replace('\\','/') if v else ''
def expect(label,src,params,children):
    templates=src['nutrition_template'];product=typed(src['product'][0]);sets={r['uuid']:r for r in src['nutrition_set']}
    if label.startswith('template'):
        template=typed(templates[0]);ext=[r for r in src['nutrition_template_ext'] if r['template_uuid']==template['uuid']]
        if label=='templateLists':template['number']=len(ext);return {'code':0,'message':'请求成功','data':{'total':1,'rows':[template]}}
        if label=='templateAll':return {'code':0,'message':'请求成功','data':{'rows':[{k:template[k] for k in ['id','uuid','name']}]}}
        rows=[]
        for raw in ext:
            row=typed(raw);row={k:row[k] for k in ['train_time','nutrition_uuid','use_volume','use_method']};row['train_time']=str(row['train_time']) if row['train_time'] else ''
            for key in ['img_path','name','power','carbohydrate','protein','fat','sodium','use_weight','use_number','use_time','employ']:row[key]=product[key]
            row['set_name']=sets[product['set_uuid']]['name'];rows.append(row)
        template['template_ext']=rows;return {'code':0,'message':'信息获取成功','data':template}
    if label in ('stationLists','stationInfo'):
        station=typed(next(r for r in src['nutrition_station'] if r['uuid']==params.get('uuid') or r['position']==params.get('keyword')));station['img_path']=media(station['img_path'])
        if label=='stationLists':return {'code':0,'message':'请求成功','data':{'total':1,'rows':[station]}}
        products={r['uuid']:r for r in src['station_link_products']};rows=[]
        for raw in src['station_ext_native_order']:
            row=typed(raw);p=products.get(row['nutrition_uuid']);row['nutrition_name']=p['name'] if p else None;row['img_path']=media(p['img_path']) if p else '';row['specifications']=p['specifications'] if p else None
            if row['num']>=11 and station['version']==2:row['num']-=2
            rows.append(row)
        station['nutrition_ext']=rows;station['file_path']=[r['file_path'] for r in src['annex_path_native_order']];station['fileUrlPrefix']='https://nutrition-media.invalid/';return {'code':0,'message':'信息获取成功','data':station}
    if label=='stationNutrition':
        row={k:product[k] for k in ['uuid','name','img_path','synopsis','specifications','manufacturer','set_uuid','use_time','supplier']};row['img_path']=media(row['img_path']);time_names={r['uuid']:r['name'] for r in src['time_cn_native_order']};row['use_time_cn']=','.join(time_names[u] for u in row['use_time'].split(',') if u in time_names);selected=[r['nutrition_uuid'] for r in src['station_ext_native_order'] if int(r['num'])==int(params['num'])];row['selected']=int(bool(selected) and selected[0]==row['uuid']);return {'code':0,'message':'请求成功','data':{'total':1,'rows':[row]}}
    if label=='setAggregate':
        rows=[]
        for raw in src['set_active_native_order']:
            count=src['set_reference_count'][raw['uuid']]
            if not count:continue
            row=typed(raw);row['number']=count;rows.append(row)
        return {'code':0,'message':'请求成功','data':rows}
    if label=='sportRoots':
        rows=copy.deepcopy(src['sport_roots_native_order'])
        for row in rows:row['children']=children.get(row['uuid'],[])
        return {'code':0,'message':'请求成功','data':rows}
    raise AssertionError(label)
for lane in ['B','C']:
    folder=private/(lane+'-http'+args.suffix);wire=[json.loads(x) for x in (folder/('private-retest-wire.jsonl' if args.suffix else 'private-wire.jsonl')).read_text().splitlines()];source=json.loads((folder/'private-http-baseline.json').read_text());children=json.loads((private/(lane+'-http')/'private-sport-source-children.json').read_text())
    check(len(wire)==(4 if args.suffix else 8),lane+':actual request count');check(source==json.loads((folder/('private-retest-final-source.json' if args.suffix else 'private-final-source.json')).read_text()),lane+':complete final scope unchanged')
    for r in wire:
        label=lane+':'+r['label'];body=r['body'];parsed=json.loads(body)
        check(r['status']==200 and parsed['code']==0,label+':HTTP/business');check(hashlib.sha256(body.encode()).hexdigest()==r['body_sha256'],label+':raw SHA');check(r['source_before']==source==r['source_after'],label+':full source/actor unchanged');check(equal(parsed,expect(r['label'],source,r['params'],children)),label+':full DTO/types/shape/sort/joins/counts')
        data=parsed['data'];check(bool(data.get('rows',data)) if isinstance(data,dict) else bool(data),label+':nonempty')
        sql=r['prepared_SQL'];check(bool(sql),label+':native DB SQL');check(all(re.match(r'^\s*(SELECT|SHOW|EXPLAIN)\b',q['queryString'],re.I) or q['queryString'].strip()=='CALL SP_TABLEDEF(?, ?)' for q in sql),label+':native readonly');check(all(q.get('ok') is True and 'error' not in q for q in sql),label+':native prepared success');check(all('ossaccesskey' not in json.dumps(q).casefold() for q in sql),label+':no OSS secret query')
        count=0
        if r['label']=='stationLists':count=int(bool(next(s for s in source['nutrition_station'] if s['position']==r['params']['keyword'])['img_path']))
        elif r['label']=='stationInfo':
            station=next(s for s in source['nutrition_station'] if s['uuid']==r['params']['uuid']);products={p['uuid']:p for p in source['station_link_products']};count=1+int(bool(station['img_path']))+sum(bool(products.get(e['nutrition_uuid'],{}).get('img_path')) for e in source['station_ext_native_order'])
        elif r['label']=='stationNutrition':count=int(bool(source['product'][0]['img_path']))
        check(r['media_leaf_calls']==count,label+':exact fixed media leaf count')
        oracle=r['oracle_native_SELECT'];check(bool(oracle) and all(q.get('queryString')==q['sql'] and re.match(r'^SELECT\b',q['queryString'],re.I) and 0<=q.get('row_count',101)<=100 for q in oracle),label+':independent PDO queryString/params/bounded rows')
        if args.suffix and lane=='B':check(body==(private/'B-http'/('private-'+r['label']+'.httpbody')).read_text(),label+':original raw MySQL bytes unchanged')
        if args.suffix:
            template=r['label'].startswith('template');cls='app\\p\\logic\\NutritionTemplate' if template else 'app\\p\\logic\\NutritionStation';sha='dd86650ab7cc953d2545f6882ea0d7e454de5c2e86cd452471271074ebd30ed6' if template else '8abad48c8164bd5eee30994700504812e61ea158bd9dfc8937da318cce07888b';check(r['reflection'][cls]['sha256']==sha,label+':Reflection actual Root source SHA')
result={'passed':all(c['passed'] for c in checks),'assertions':len(checks),'mode':'native date' if args.native_date else 'MySQL seconds contract','failures':[c['label'] for c in checks if not c['passed']]};print(json.dumps(result,ensure_ascii=False,indent=2));raise SystemExit(0 if result['passed'] else 2)
