<?php
declare(strict_types=1);
$lane=(string)getenv('PILOT_LANE');$root=(string)getenv('PILOT_TARGET_ROOT');$mode=$argv[1]??'';$stage='preflight';
set_exception_handler(static function(Throwable $e)use(&$stage,$lane){echo json_encode(['status'=>'stop','lane'=>$lane,'stage'=>$stage,'class'=>get_class($e),'message_sha'=>hash('sha256',$e->getMessage())]).PHP_EOL;exit(2);});
if(!in_array($lane,['B','C'],true)||!in_array($mode,['preflight','run','audit'],true)||PHP_VERSION_ID<70300||PHP_VERSION_ID>=70400)exit(78);
$cfg=require $root.'/application/database.php';
if($lane==='B'){
 $local=require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
 foreach(['hostname','database','username','password'] as $k)if((string)($cfg[$k]??'')!==(string)($local[$k]??''))throw new RuntimeException('Not code local');
 if((int)($cfg['hostport']??3306)!==(int)($local['hostport']??3306))throw new RuntimeException('Not local port');
 $dsn='mysql:host='.$cfg['hostname'].';port='.$cfg['hostport'].';dbname='.$cfg['database'].';charset='.$cfg['charset'];
}else{
 if($cfg['hostname']!=='127.0.0.1'||(int)$cfg['hostport']!==5237||$cfg['database']!=='ZHCT_APP')throw new RuntimeException('Not approved DM');
 $dsn='dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
}
$connect=static function()use($dsn,$cfg){return new PDO($dsn,$cfg['username'],$cfg['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER,PDO::ATTR_TIMEOUT=>5]);};
$db=$connect();$q=static function(PDO $d,string $sql,array $a=[]){$s=$d->prepare($sql);$s->execute($a);return $s->fetchAll(PDO::FETCH_ASSOC);};
$tables=['consume_limit','consume_limit_account','consume_limit_department','consume_limit_scenario','consume_limit_scope'];
$meta=[];foreach($tables as $t){$meta[$t]=$lane==='B'?$q($db,'SHOW COLUMNS FROM ydy_'.$t):$q($db,'SELECT COLUMN_NAME,DATA_TYPE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID',['YDY_'.strtoupper($t)]);if(!$meta[$t])throw new RuntimeException('Required table absent');}
$source=[];foreach(['/application/api/controller/ConsumeLimit.php','/application/api/service/ConsumeLimit.php','/application/api/validate/ConsumeLimit.php'] as $f)$source[$f]=hash_file('sha256',$root.$f);
if($mode==='preflight'){echo json_encode(['lane'=>$lane,'status'=>'ready','schema'=>$meta,'source'=>$source,'http_sent'=>false],JSON_UNESCAPED_UNICODE).PHP_EOL;exit;}
if($mode==='audit'){$saved=json_decode(file_get_contents(__DIR__.'/result-'.$lane.'.json'),true);$marker=$saved['marker'];$ids=$saved['owned_ids'];$counts=['user'=>count($q($db,'SELECT ID FROM ydy_user WHERE ACCOUNT=? AND UUID=?',[$marker,$marker.'U']))];foreach($tables as $t)$counts[$t]=count($q($db,'SELECT ID FROM ydy_'.$t.' WHERE '.($t==='consume_limit'?'limit_name=?':'limit_id=?'),[$t==='consume_limit'?$marker:$ids['limit']]));echo json_encode(['status'=>array_sum($counts)===0?'independent_zero_pass':'cleanup_fail','lane'=>$lane,'counts'=>$counts]).PHP_EOL;exit(array_sum($counts)===0?0:2);}
$base='http://127.0.0.1:'.($lane==='B'?18084:18085);
$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);
if(($boundary['external_stubs']??null)!==true||($boundary['redis_isolated']??null)!==true||($boundary['real_queue']??true)!==false)throw new RuntimeException('Unsafe boundary');
$marker='CDXCL10'.strtoupper(bin2hex(random_bytes(5)));$actor=$marker.'U';$dept=$marker.'D';$id=0;$actorId=0;$samples=[];$checks=[];$states=[];
$result=['status'=>'failed','lane'=>$lane,'marker'=>$marker,'source'=>$source,'schema_column_counts'=>array_map('count',$meta)];
$read=static function()use($db,$q,&$id,$tables){$out=[];foreach($tables as $t)$out[$t]=$q($db,'SELECT * FROM ydy_'.$t.' WHERE '.($t==='consume_limit'?'id':'limit_id').'=? ORDER BY ID',[$id]);return $out;};
try{
 $stage='own_actor';$s=$db->prepare('INSERT INTO ydy_user (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,STATUS,CREATE_TIME,CREATE_BY,IS_DELETE) VALUES (?,?,?,?,?,0,0,?,?,0)');
 $s->execute([$actor,$marker,'DISABLED','CDX_DISABLED',$marker,date('Y-m-d H:i:s'),$marker]);
 $actors=$q($db,'SELECT ID FROM ydy_user WHERE UUID=? AND ACCOUNT=? AND CREATE_BY=?',[$actor,$marker,$marker]);if(count($actors)!==1)throw new RuntimeException('Actor ambiguous');$actorId=(int)$actors[0]['id'];
 if(count($q($db,'SELECT UUID FROM ydy_department WHERE UUID=?',[$dept]))!==0||count($q($db,'SELECT STAFF_UUID FROM ydy_staff_department WHERE DEPARTMENT_UUID=?',[$dept]))!==0)throw new RuntimeException('Synthetic department collision');
 require_once $root.'/application/base/Security.php';$token=\app\base\Security::encrypt(implode("\t",[(string)$actorId,$actor,$marker,'0','','',$marker]));
 $http=static function(string $label,string $action,array $params)use($base,$token,&$samples,&$stage){$stage='http_'.$label;$ctx=stream_context_create(['http'=>['method'=>'POST','timeout'=>15,'ignore_errors'=>true,'follow_location'=>0,'header'=>"Content-Type: application/x-www-form-urlencoded;charset=UTF-8\r\ntoken: ".$token."\r\n",'content'=>http_build_query($params,'','&',PHP_QUERY_RFC3986)]]);$raw=file_get_contents($base.'/api/consumeLimit/'.$action,false,$ctx);preg_match('/HTTP\/\S+\s+(\d+)/',$http_response_header[0]??'',$m);$body=json_decode($raw,true);$samples[$label]=['action'=>$action,'http'=>(int)($m[1]??0),'body'=>$body];if($samples[$label]['http']!==200||!is_array($body))throw new RuntimeException('Invalid HTTP');return $body;};
 $data=['limitName'=>$marker,'departmentUuids'=>[$dept],'scenarios'=>[1,2],'limitTypes'=>[2],'accountTypes'=>['cash','subsidy'],'status'=>0,'monthlyCount'=>13,'dailyCount'=>3,'mealCount'=>1,'remark'=>'兼容测试金额12.34，停用且不关联真实员工'];
 $r=$http('create','create',$data);if(($r['code']??null)!==0)throw new RuntimeException('Create rejected');$id=(int)$r['data']['id'];if($id<=0)throw new RuntimeException('No id');$state=$read();$states['create']=$state;$checks['create']=count($state['consume_limit'])===1&&count($state['consume_limit_account'])===2&&count($state['consume_limit_department'])===1&&count($state['consume_limit_scenario'])===2&&(int)$state['consume_limit'][0]['status']===0;
 $checks['create_input_values']=(int)$state['consume_limit'][0]['monthly_limit']===13&&(int)$state['consume_limit'][0]['daily_limit']===3&&(int)$state['consume_limit'][0]['meal_limit']===1&&$state['consume_limit'][0]['remark']===$data['remark']&&$state['consume_limit_department'][0]['department_uuid']===$dept;
 $checks['seconds_time_format']=preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/D',(string)$r['data']['create_time'])===1&&preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/D',(string)$r['data']['update_time'])===1;
 $http('detail','detail',['id'=>$id]);
 $http('list_filter','lists',['limitName'=>$marker,'page'=>1,'pageSize'=>1,'status'=>0,'limitType'=>2,'scenario'=>1,'departmentUuid'=>[$dept]]);
 $http('list_page2','lists',['limitName'=>$marker,'page'=>2,'pageSize'=>1]);
 $http('list_status_miss','lists',['limitName'=>$marker,'status'=>1]);
 $data['id']=$id;$data['monthlyCount']='-9';$data['dailyCount']='0';$data['mealCount']='';$data['accountTypes']='gift,cash,gift';$data['scenarios']='3,3';$data['limitTypes']=[1,2];$data['scope']=[['stall_id'=>1,'meal_times'=>1],['stall_id'=>1,'meal_times'=>1],['stall_id'=>1,'meal_times'=>99]];
 usleep(1100000); // Distinguish update-time behavior from coincident same-second timestamps.
 $r=$http('update','update',$data);if(($r['code']??null)!==0)throw new RuntimeException('Update rejected');$state=$read();$states['update']=$state;$checks['update']=(int)$state['consume_limit'][0]['monthly_limit']===-1&&(int)$state['consume_limit'][0]['daily_limit']===0&&(int)$state['consume_limit'][0]['meal_limit']===-1&&count($state['consume_limit_account'])===2&&count($state['consume_limit_scenario'])===1;
 $checks['scope_deduplicate']=count($state['consume_limit_scope'])===1&&(int)$state['consume_limit_scope'][0]['stall_id']===1&&(int)$state['consume_limit_scope'][0]['meal_times']===1;
 $checks['update_time_advances']=strcmp((string)$r['data']['update_time'],(string)$samples['create']['body']['data']['update_time'])>0;
 $http('list_scope','lists',['limitName'=>$marker,'restaurantIds'=>[1],'limitType'=>1,'scenario'=>3]);
 usleep(1100000);
 $repeat=$http('repeat_update','update',$data);$states['repeat_update']=$read();$checks['repeat_update']=count($states['repeat_update']['consume_limit_account'])===2;
 $checks['repeat_update_keeps_time']=$repeat['data']['update_time']===$samples['update']['body']['data']['update_time'];
 $checks['create_time_unchanged']=$repeat['data']['create_time']===$samples['create']['body']['data']['create_time'];
 $http('status_on','changeStatus',['id'=>$id,'status'=>1]);$states['status_on']=$read();$checks['status_on']=(int)$states['status_on']['consume_limit'][0]['status']===1;
 $checks['status_on_time_advances']=substr((string)$states['status_on']['consume_limit'][0]['update_time'],0,19)>substr((string)$states['repeat_update']['consume_limit'][0]['update_time'],0,19);
 $http('status_off','changeStatus',['id'=>$id,'status'=>0]);$states['status_off']=$read();$checks['status_off']=(int)$states['status_off']['consume_limit'][0]['status']===0;
 $state=$read();$http('invalid_accounts','update',array_merge($data,['accountTypes'=>['invalid']]));$checks['validation_no_write']=$read()===$state;
 $http('invalid_status','changeStatus',['id'=>$id,'status'=>2]);$checks['invalid_status_no_write']=$read()===$state;
 $http('create_missing','create',[]);$http('delete_missing','delete',[]);
 $http('delete','delete',['id'=>$id]);$state=$read();$states['delete']=$state;$checks['delete']=(int)$state['consume_limit'][0]['is_delete']===1&&count($state['consume_limit_account'])===0&&count($state['consume_limit_department'])===0&&count($state['consume_limit_scenario'])===0&&count($state['consume_limit_scope'])===0;
 $http('repeat_delete','delete',['id'=>$id]);$http('deleted_detail','detail',['id'=>$id]);
 $checks['status_delete_second_time_format']=true;
 foreach(['status_on','status_off','delete'] as $key){$row=$states[$key]['consume_limit'][0];if(preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.0+)?$/D',(string)$row['update_time'])!==1||substr((string)$row['create_time'],0,19)!==substr((string)$states['create']['consume_limit'][0]['create_time'],0,19))$checks['status_delete_second_time_format']=false;}
 $checks['delete_time_not_older']=substr((string)$states['delete']['consume_limit'][0]['update_time'],0,19)>=substr((string)$states['status_off']['consume_limit'][0]['update_time'],0,19);
 foreach($samples as $label=>$sample){$expected=in_array($label,['invalid_accounts','invalid_status','create_missing','delete_missing','repeat_delete','deleted_detail'],true)?1:0;$checks['code_'.$label]=($sample['body']['code']??null)===$expected;}
 $checks['scope_filter_total']=($samples['list_scope']['body']['data']['total']??null)===1;
 $checks['filtered_total']=($samples['list_filter']['body']['data']['total']??null)===1;
 $checks['page2_empty']=($samples['list_page2']['body']['data']['data']??null)===[];
 $checks['status_miss_empty']=($samples['list_status_miss']['body']['data']['data']??null)===[];
 $checks['source_unchanged_during_run']=true;foreach($source as $file=>$hash){if(!hash_equals($hash,(string)hash_file('sha256',$root.$file)))$checks['source_unchanged_during_run']=false;}
 $result['status']=!in_array(false,$checks,true)?'pass':'assertion_failed';
}finally{
 $stage='cleanup';if($id===0){$found=$q($db,'SELECT ID FROM ydy_consume_limit WHERE LIMIT_NAME=? AND CREATE_BY=?',[$marker,$marker]);if(count($found)===1)$id=(int)$found[0]['id'];}if($id>0){$own=$q($db,'SELECT ID FROM ydy_consume_limit WHERE ID=? AND LIMIT_NAME=? AND CREATE_BY=?',[$id,$marker,$marker]);if(count($own)===1){foreach(array_reverse($tables) as $t){$s=$db->prepare('DELETE FROM ydy_'.$t.' WHERE '.($t==='consume_limit'?'ID=? AND LIMIT_NAME=? AND CREATE_BY=?':'LIMIT_ID=?'));$s->execute($t==='consume_limit'?[$id,$marker,$marker]:[$id]);}}}
 if($actorId>0){$s=$db->prepare('DELETE FROM ydy_user WHERE ID=? AND UUID=? AND ACCOUNT=? AND CREATE_BY=?');$s->execute([$actorId,$actor,$marker,$marker]);}
 $result['owned_ids']=['actor'=>$actorId,'limit'=>$id];$result['checks']=$checks;$result['samples']=$samples;$result['db_states']=$states;
 file_put_contents(__DIR__.'/result-'.$lane.'.json',json_encode($result,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));echo json_encode(['lane'=>$lane,'status'=>$result['status'],'samples'=>count($samples),'checks'=>$checks]).PHP_EOL;
}
exit($result['status']==='pass'?0:2);
