#!/usr/bin/env python3
"""Root-reviewed finished batches only. No HTTP, DB, or closed-gate claims."""
import hashlib
from pathlib import Path
import subprocess
import sys
import integrate_20261010 as tool

PAY=tool.AI+'parallel-ai-payment-template-config-20261010/'
ROLE=tool.AI+'parallel-ai-role-menu-read-20261010/'
NUT=tool.AI+'parallel-ai-nutrition-plan-target-20261010/'
CITY=tool.STORE+'parallel-store-city-read-20261010/'
MARKET=tool.STORE+'parallel-store-marketing-strategy-20261010/'

def run(relative):
    subprocess.run([sys.executable,str(tool.ROOT/relative)],check=True,capture_output=True)

def source_hashes(repo,expected):
    for file,sha in expected.items():
        assert hashlib.sha256((tool.ROOT.parent/'zhctproject'/repo/file).read_bytes()).hexdigest()==sha

def verify_pay():
    run(PAY+'compare.py')
    source_hashes('ai_api',tool.load(PAY+'source-manifest.json')['source_sha256'])

def verify_role():
    r=tool.load(ROLE+'results.json');assert r['status']=='pass' and r['full_cross_engine_response_equal'] is False
    assert len(r['methods'])==4
    cleanup=tool.load(ROLE+'RESOURCE_POSTCHECK.json')
    for lane,port in [('B',18371),('C',18372)]:
        x=r['lanes'][lane]
        assert x['cases']==x['source_pass']==12 and x['nonempty_methods']==4
        assert x['natural_unchanged'] and x['dml']==x['ddl']==x['external_attempts']==0 and x['token_cleaned']
        assert cleanup[lane]=={f'port_{port}_listening':False,'private_cache_files':0,'state_file_exists':False}
        source_hashes('ai_api',x['source_sha256'])

def verify_nut():
    run(NUT+'compare.py');run(NUT+'test_compare.py')
    r=tool.load(NUT+'manifest.json');assert r['new_methods_count']==6 and r['final_strict_http_pairs']==12
    assert r['strict_wire_differences_after_fix']==0 and r['real_internal_job_pairs_post_fix']==1
    c=tool.load(NUT+'resource-cleanup.json')
    assert c['B_original_worker_stopped'] and c['B_source_identity_reprobe_worker_stopped'] and c['C_worker_stopped']
    assert c['B_port_18321_listeners']==c['C_port_18322_listeners']==c['cache_file_count_each']==0
    source_hashes('ai_api',{'app/store/model/nutrition/Nutrition.php':'a68cdfa33dfe1cac94823b546e43e0e4550185db6a1349fd699b2574989db9a9',
        'app/store/service/nutrition/NutritionTarget.php':'b94d0e4d1bddc46c7ac8be3d7cf6b2d9c164c9e2eb061d79b85e4ae4761aef7b'})

def verify_city():
    run(CITY+'compare.py')
    source_hashes('store',tool.load(CITY+'results.json')['lanes']['B']['source_files'])

def verify_market():
    run(MARKET+'compare.py')
    source_hashes('store',{'application/api/controller/MarketingStrategy.php':'1373b372b75cee302bd8e2afd550dbdb42bf57d034c38c69a24cda41ce701212',
        'application/api/service/MarketingStrategy.php':'bbd667d3fbd8ada513004e3cfcbe308b96d9925de165577a4dd41bbf41f3292b'})

def target(repo,method,path,module,evidence,scope,**extra):
    return dict(repo=repo,method=method,path=path,module=module,evidence=evidence+'REPORT.md',scope=scope,
        terminal='PC-or-terminal-see-report',role='isolated-approved-synthetic-or-natural-readonly-context',
        risk='read' if method=='GET' else 'configuration-or-read-see-report',
        cleanup=extra.pop('cleanup','retained-owned-marked-data-where-written-credentials-cache-workers-released'),**extra)

def specs():
    pay=[target('ai_api','GET' if action in ['list','all','detail'] else 'POST',f'/{app}/setting.payment.Template/{action}',
        '支付模板配置',PAY,'ai-primary-own-payment-template-two-tenant-no-binding-certificate-or-payment',
        request_type='GET-query' if action in ['list','all','detail'] else 'PC-multipart-no-files-or-JSON-delete',
        parity='partial-evidence-50-pairs-full-wire-source-verified-owned-ID-auto-time-only-mapped',
        boundary_empty_min_max='partial-evidence-JSON-CLOB-newline-string-number-NULL-object-array-and-cross-owned-tenant')
        for app in ['store','terminal'] for action in ['list','all','detail','add','edit','delete']]
    role=[target('ai_api','GET',item['path'],'角色菜单只读树',ROLE,'ai-primary-four-natural-tables-readonly-original-scope',
        parity='partial-evidence-12-cases-each-source-verified-natural-snapshots-differ-not-full-cross-response-equal',
        cleanup='four-natural-source-SHA-unchanged-zero-DML-DDL-private-token-cache-worker-released')
        for item in tool.load(ROLE+'results.json')['methods']]
    nut=[target('ai_api','POST',p,'营养方案及目标',NUT,'ai-primary-owned-plan-target-staff-and-real-Serve-recommend-history-no-network',
        request_type='current-PC-JSON-staff_id-form-see-report',
        parity='partial-evidence-12-pairs-source-wire-types-values-order-after-narrow-default-and-decimal-fixes',
        boundary_empty_min_max='partial-evidence-three-date-range-repeat-same-PK-middle-decimal-rounding-today-Job')
        for p in ['/store/nutrition.Nutrition/add','/store/nutrition.Nutrition/lists','/store/nutrition.Nutrition/delete',
            '/store/nutrition.NutritionTarget/add','/store/nutrition.NutritionTarget/detail','/store/nutrition.NutritionTarget/getFlagDate']]
    city=[target('store','POST','/p/city/lists','城市树读取',CITY,'store-primary-four-owned-City-fixture-rows-readonly-HTTP',
        request_type='signed-form-private-synthetic-token',
        parity='partial-evidence-two-pairs-complete-original-tree-types-values-order-equal',
        cleanup='retained-four-marked-City-fixtures-no-User-write-workers-stopped',
        performance='not-tested-SQL-fingerprint-completeness-open-two-C-lexical-warnings')]
    market=[target('store','POST','/api/marketingStrategy/'+a,'营销策略配置',MARKET,
        'store-primary-seven-owned-config-tables-expired-own-stall-no-wallet-order-or-external-effects',
        request_type='current-PC-JSON',
        parity='partial-evidence-14-cases-13-full-responses-one-owned-select-projection-source-verified',
        duplicate_retry='partial-evidence-repeat-create-refusal-noop-update-keeps-time-change-updates-time',
        cleanup='retained-two-softdeleted-status0-expired-strategies-and-original-two-mealtime-relations-each-resources-released')
        for a in ['lists','create','update','delete','detail','changeStatus','select']]
    return [(16,pay,verify_pay),(17,role,verify_role),(18,nut,verify_nut),(19,city,verify_city),(20,market,verify_market)]

if __name__=='__main__':
    batches=specs()
    for _,_,verify in batches:verify()
    for number,items,verify in batches:
        audit=Path(__file__).with_name(f'integration-20261010-batch{number}.json')
        if audit.exists():raise RuntimeError('Preserve original transition audit; do not rerun completed batch '+str(number))
        tool.main(items,audit,verify)
