<?php
declare(strict_types=1);
date_default_timezone_set('Asia/Shanghai');$lane=getenv('CATEGORY_LANE');$runtime=getenv('CATEGORY_RUNTIME');$root=getenv('CATEGORY_ROOT');$base=getenv('CATEGORY_BASE');$dm=getenv('POC_DIALECT')==='dm';
$db=new PDO($dm?'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP':'mysql:host='.getenv('POC_AI_HOST').';port='.getenv('POC_AI_PORT').';dbname='.getenv('POC_AI_SCHEMA').';charset=utf8',getenv('POC_AI_USER'),getenv('POC_AI_PASSWORD'),[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);
$q=static function(string $sql,array $args=[])use($db):array{$s=$db->prepare($sql);$s->execute($args);$r=[];while($v=$s->fetch(PDO::FETCH_ASSOC)){$v=array_change_key_case($v,CASE_LOWER);foreach($v as &$x)if(is_resource($x))$x=stream_get_contents($x);unset($x);$r[]=$v;if(count($r)>2000)throw new RuntimeException('Small source cap');}return $r;};$check=static function(bool $ok,string $why):void{if(!$ok)throw new RuntimeException($why);};$hash=static function($v):string{return hash('sha256',serialize($v));};
$shape=static function($v)use(&$shape){if(is_object($v)){$r=[];foreach(get_object_vars($v) as $k=>$x)$r[$k]=$shape($x);ksort($r);return ['object'=>$r];}return is_array($v)?['array'=>array_map($shape,$v)]:(is_int($v)||is_float($v)?'number':gettype($v));};
$before=['payment_template'=>$q('SELECT * FROM yoshop_payment_template ORDER BY template_id'),'payment'=>$q('SELECT * FROM yoshop_payment ORDER BY payment_id')];
define('TP_ENV','poc');define('BUSINESS','aizhct');define('ENTERPRISE_NO','10001');require $root.'/vendor/autoload.php';$app=new \think\App($root);$app->setEnvName('poc');$app->setRuntimePath($runtime.'/fixture-runtime/');$app->initialize();$cache=new \think\cache\driver\File($app,['path'=>$runtime.'/cache/','prefix'=>'','expire'=>0]);
$tokens=[];foreach(['store'=>2147000701,'terminal'=>2147000702] as $kind=>$tenant){$existing=$q('SELECT store_id,store_name FROM yoshop_store WHERE store_id=?',[$tenant]);$check($existing===[]||(count($existing)===1&&$existing[0]['store_name']==='CDXPAYTPL10_'.$kind&&$q('SELECT template_id FROM yoshop_payment_template WHERE store_id=?',[$tenant])===[]),'Synthetic tenant setup-only recovery requires zero templates');$cols=$dm?$q("SELECT COLUMN_NAME,DATA_TYPE FROM USER_TAB_COLUMNS WHERE TABLE_NAME='YOSHOP_STORE'"):$q("SELECT COLUMN_NAME,DATA_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='yoshop_store'");$types=[];foreach($cols as $c)$types[strtolower($c['column_name'])]=strtolower($c['data_type']);$data=['store_id'=>$tenant,'store_name'=>'CDXPAYTPL10_'.$kind,'describe'=>'CDX payment template isolated configuration fixture'];foreach(['create_time','update_time'] as $f)if(isset($types[$f]))$data[$f]=strpos($types[$f],'time')!==false?date('Y-m-d H:i:s'):time();$names=array_map(static function($k)use($dm){return $dm?'"'.strtoupper($k).'"':'`'.$k.'`';},array_keys($data));if(!$existing)$db->prepare('INSERT INTO yoshop_store ('.implode(',',$names).') VALUES ('.implode(',',array_fill(0,count($data),'?')).')')->execute(array_values($data));$check(count($q('SELECT store_id FROM yoshop_store WHERE store_id=? AND store_name=?',[$tenant,$data['store_name']]))===1,'Synthetic tenant insert ownership');$tokens[$kind]=bin2hex(random_bytes(24));$cache->set($tokens[$kind],['user'=>['store_user_id'=>2147000799,'store_id'=>$tenant,'real_name'=>'CDXPAYTPL10','user_name'=>'CDXPAYTPL10','is_super'=>1],'store_id'=>$tenant,'is_login'=>true],900);}
$report=['lane'=>$lane,'cases'=>[],'owned'=>[],'source_before'=>array_map(static function($rows)use($hash){return ['rows'=>count($rows),'sha256'=>$hash($rows)];},$before),'business_writes'=>'only two synthetic store fixtures and four owned payment templates','error'=>null];$privateBodies=[];$stage='boundary';
$default=['wechat'=>['mchType'=>'normal','version'=>'v2','normal'=>['appId'=>'','mchId'=>'','apiKey'=>'','apiclientCert'=>'','apiclientKey'=>''],'provider'=>['spAppId'=>'','spMchId'=>'','spApiKey'=>'','subAppId'=>'','subMchId'=>'','spApiclientCert'=>'','spApiclientKey'=>'']],'alipay'=>['appId'=>'','signType'=>'RSA2','signMode'=>10,'alipayPublicKey'=>'','appCertPublicKey'=>'','alipayCertPublicKey'=>'','alipayRootCert'=>'','merchantPrivateKey'=>'']];
$merge=static function(array $a,array $b)use(&$merge):array{$r=[];foreach($a+$b as $k=>$v)$r[$k]=(isset($a[$k])&&is_array($a[$k])&&isset($b[$k])&&is_array($b[$k]))?((!empty($a[$k])&&array_keys($a[$k])!==range(0,count($a[$k])-1))?$merge($a[$k],$b[$k]):$b[$k]):($b[$k]??$a[$k]??null);return $r;};
$dto=static function(array $row)use($default,$merge):array{$row['config']=$merge($default,json_decode((string)$row['config'],true));foreach(['template_id','sort','is_delete','store_id'] as $k)$row[$k]=(int)$row[$k];foreach(['create_time','update_time'] as $k)if(isset($row[$k]))$row[$k]=is_numeric($row[$k])?date('Y-m-d H:i:s',(int)$row[$k]):date('Y-m-d H:i:s',strtotime($row[$k]));return $row;};
$source=static function(int $tenant)use($q):array{return $q('SELECT * FROM yoshop_payment_template WHERE store_id=? ORDER BY sort ASC,template_id ASC',[$tenant]);};
$rq=static function(string $kind,string $scenario,string $action,array $params=[])use($base,$tokens,$shape,&$report,&$privateBodies):array{$method=in_array($action,['list','all','detail'],true)?'GET':'POST';$path='/'.$kind.'/setting.payment.'.($kind==='store'?'template':'Template').'/'.$action;$header='Access-Token: '.$tokens[$kind]."\r\nPlatform: PC\r\nX-Pilot-Scenario: PILOT_DICT_TPL_".strtoupper($kind.'_'.$scenario)."\r\n";$opts=['method'=>$method,'header'=>$header,'timeout'=>20,'ignore_errors'=>true];$url=$base.$path;
 if($method==='GET')$url.='?'.http_build_query($params);elseif(in_array($action,['add','edit'],true)){$boundary='CDX'.bin2hex(random_bytes(12));$body='';foreach($params as $k=>$v)$body.='--'.$boundary."\r\nContent-Disposition: form-data; name=\"".$k."\"\r\n\r\n".$v."\r\n";$opts['header'].='Content-Type: multipart/form-data; boundary='.$boundary."\r\n";$opts['content']=$body.'--'.$boundary."--\r\n";}else{$opts['header'].="Content-Type: application/json\r\n";$opts['content']=json_encode($params);}
 $raw=@file_get_contents($url,false,stream_context_create(['http'=>$opts]));$body=json_decode((string)$raw,true);$wire=json_decode((string)$raw);preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$m);$entry=['scenario'=>$kind.'_'.$scenario,'method'=>$method,'path'=>$path,'http_status'=>(int)($m[1]??0),'status'=>$body['status']??null,'wire_shape'=>$shape($wire),'raw_response_sha256'=>hash('sha256',(string)$raw)];$report['cases'][]=$entry;$privateBodies[$kind.'_'.$scenario]=$wire;return [$body,count($report['cases'])-1];};
$verify=static function(string $kind,string $label,string $action,array $params=[])use($rq,$source,$dto,$check,&$report):void{$tenant=$kind==='store'?2147000701:2147000702;[$body,$index]=$rq($kind,$label,$action,$params);$check(($body['status']??null)===200,'Template positive status '.$kind.'_'.$label);$rows=$source($tenant);$expected=array_map($dto,array_values(array_filter($rows,static function($r){return (int)$r['is_delete']===0;})));$actual=$body['data'];
 if($action==='all')$ok=$actual===['list'=>$expected];elseif($action==='detail'){$matches=array_values(array_filter($rows,static function($r)use($params){return (int)$r['template_id']===(int)$params['templateId'];}));$ok=$actual===['detail'=>$matches?$dto($matches[0]):null];}else{$list=$actual['list'];$page=(int)($params['page']??1);$ok=$list['data']===array_slice($expected,($page-1)*15,15)&&$list['total']===count($expected)&&$list['per_page']===15&&$list['current_page']===$page;}
 $report['cases'][$index]['complete_source_projection_equal']=$ok;$report['cases'][$index]['source_row_count']=count($expected);$check($ok,'All field/value/type/order source projection '.$kind.'_'.$label);};
try{$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);$check($boundary['allowlist']==='payment-template-v2-alipay20-only'&&$boundary['certificate_and_filesystem_tripwire'],'Private boundary');
 foreach(['store'=>2147000701,'terminal'=>2147000702] as $kind=>$tenant){$stage=$kind.'_empty';$verify($kind,'empty_list','list');$verify($kind,'empty_all','all');$items=[];
  foreach(['wechat','alipay'] as $method){$stage=$kind.'_add_'.$method;$config=[$method=>$default[$method]];if($method==='alipay')$config[$method]['signMode']=20;$config[$method]['cdx_fixture']=['中文'=>'引号"斜线\\换行\n','empty_array'=>[],'empty_object'=>(object)[],'null'=>null,'number'=>1,'number_text'=>'1','flag'=>true];$payload=['name'=>'CDXTPL10_'.$kind.'_'.$method,'method'=>$method,'config'=>json_encode($config,JSON_UNESCAPED_UNICODE),'remarks'=>'CDX中文"备注\\','sort'=>7];[$body,$index]=$rq($kind,'add_'.$method,'add',$payload);$check(($body['status']??null)===200,'Real add '.$kind.'_'.$method);$matches=array_values(array_filter($source($tenant),static function($r)use($payload){return $r['name']===$payload['name']&&$r['method']===$payload['method'];}));$check(count($matches)===1,'Exact own template added');$row=$matches[0];$check(json_decode((string)$row['config'],true)===json_decode($payload['config'],true),'Stored JSON raw projection');$items[$method]=['id'=>(int)$row['template_id'],'payload'=>$payload,'config'=>$config];$report['owned'][]=['kind'=>$kind,'tenant'=>$tenant,'template_id'=>(int)$row['template_id'],'marker'=>$payload['name']];$report['cases'][$index]['added_source_json_equal']=true;}
  $stage=$kind.'_reads';$verify($kind,'list_two','list');$verify($kind,'all_two','all');foreach($items as $method=>$item)$verify($kind,'detail_'.$method,'detail',['templateId'=>$item['id']]);
  foreach(['wechat','alipay'] as $method){$stage=$kind.'_edit_'.$method;$item=&$items[$method];if($method==='wechat')$item['config']['wechat']['mchType']='provider';$item['config'][$method]['cdx_fixture']['number_text']='001';$item['payload']['config']=json_encode($item['config'],JSON_UNESCAPED_UNICODE);$item['payload']['sort']=$method==='wechat'?0:7;$item['payload']['remarks']='CDX编辑中文\\"';$item['payload']['templateId']=$item['id'];[$body,$index]=$rq($kind,'edit_'.$method,'edit',$item['payload']);$check(($body['status']??null)===200,'Real edit');$stored=$q('SELECT * FROM yoshop_payment_template WHERE template_id=? AND store_id=?',[$item['id'],$tenant]);$check(count($stored)===1&&json_decode((string)$stored[0]['config'],true)===json_decode($item['payload']['config'],true)&&$stored[0]['remarks']===$item['payload']['remarks']&&(int)$stored[0]['sort']===$item['payload']['sort'],'Edited source JSON/remarks/sort');$report['cases'][$index]['edited_source_equal']=true;$verify($kind,'detail_edited_'.$method,'detail',['templateId'=>$item['id']]);unset($item);}
  $stage=$kind.'_repeat';[$body,$index]=$rq($kind,'edit_repeat','edit',$items['alipay']['payload']);$check(($body['status']??null)===200,'Original repeat edit status');$verify($kind,'detail_repeat','detail',['templateId'=>$items['alipay']['id']]);
  $stage=$kind.'_delete';$id=$items['wechat']['id'];$check($q('SELECT payment_id FROM yoshop_payment WHERE template_id=?',[$id])===[],'Own template globally unreferenced');[$body,$index]=$rq($kind,'delete_wechat','delete',['templateId'=>$id]);$check(($body['status']??null)===200,'Actual soft delete');$row=$q('SELECT is_delete FROM yoshop_payment_template WHERE template_id=? AND store_id=?',[$id,$tenant]);$check(count($row)===1&&(int)$row[0]['is_delete']===1,'Owned soft deletion retained');$report['cases'][$index]['soft_delete_source_verified']=true;$verify($kind,'detail_after_delete','detail',['templateId'=>$id]);$verify($kind,'list_after_delete','list');$verify($kind,'all_after_delete','all');[$body,$index]=$rq($kind,'delete_repeat','delete',['templateId'=>$id]);$check(($body['status']??null)===200,'Original repeated soft delete');$verify($kind,'missing_detail','detail',['templateId'=>2147000790]);
 }
 $stage='cross-tenant';foreach(['store','terminal'] as $kind){$other=$kind==='store'?'terminal':'store';$target=array_values(array_filter($report['owned'],static function($v)use($other){return $v['kind']===$other;}))[0];$beforeCross=$q('SELECT * FROM yoshop_payment_template WHERE store_id IN (?,?) ORDER BY template_id',[2147000701,2147000702]);$verify($kind,'other_tenant_detail','detail',['templateId'=>$target['template_id']]);[$body,$index]=$rq($kind,'other_tenant_delete','delete',['templateId'=>$target['template_id']]);$check(($body['status']??null)!==200,'Cross-tenant delete cannot succeed');$report['cases'][$index]['owned_both_tenants_unchanged']=$q('SELECT * FROM yoshop_payment_template WHERE store_id IN (?,?) ORDER BY template_id',[2147000701,2147000702])===$beforeCross;$check($report['cases'][$index]['owned_both_tenants_unchanged'],'Cross tenant writes unchanged');}
}catch(Throwable $e){$report['error']=['stage'=>$stage,'class'=>get_class($e),'message'=>$e->getMessage()];}
finally{foreach($tokens as $token)$cache->delete($token);$afterTemplates=$q('SELECT * FROM yoshop_payment_template WHERE store_id NOT IN (?,?) ORDER BY template_id',[2147000701,2147000702]);$afterPayment=$q('SELECT * FROM yoshop_payment ORDER BY payment_id');$report['cleanup']=['all_tokens_absent'=>true,'natural_templates_unchanged'=>$hash($afterTemplates)===$hash($before['payment_template']),'payment_bindings_unchanged'=>$hash($afterPayment)===$hash($before['payment']),'templates_intentionally_retained'=>count($q('SELECT template_id FROM yoshop_payment_template WHERE store_id IN (?,?)',[2147000701,2147000702])),'stores_intentionally_retained'=>2,'tripwire_calls'=>is_file($runtime.'/tripwire.jsonl')?count(file($runtime.'/tripwire.jsonl')):0,'queue_or_user_stub_calls'=>is_file($root.'/runtime/poc-stub-hits.log')?count(file($root.'/runtime/poc-stub-hits.log')):0];foreach($tokens as $token)$report['cleanup']['all_tokens_absent']=$report['cleanup']['all_tokens_absent']&&$cache->get($token)===null;
 file_put_contents($runtime.'/private-bodies.json',json_encode($privateBodies,JSON_UNESCAPED_UNICODE));chmod($runtime.'/private-bodies.json',0600);file_put_contents($runtime.'/report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));chmod($runtime.'/report.json',0600);}
echo json_encode(['lane'=>$lane,'cases'=>count($report['cases']),'error'=>$report['error'],'cleanup'=>$report['cleanup'],'report'=>$runtime.'/report.json']),"\n";exit($report['error']?2:0);
