<?php
declare(strict_types=1);
require __DIR__.'/bootstrap.php';
if(($argv[1]??'')==='audit'){echo json_encode(['lane'=>$lane,'owned_counts'=>$counts()]),"\n";exit(array_sum($counts())?1:0);}
$check(array_sum($counts())===0,'Private staff collision');$before=$snapshot();$healthBefore=$health();
$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);
$check(($boundary['queue_capture_mode']??'')==='HABIT_OWN_STAFF_NO_QUEUE_V1'&&!empty($boundary['external_stubs']),'Actual queue boundary');
$root=(string)getenv('CATEGORY_ROOT');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($tenants as $module=>$tenant){$tokens[$module]=bin2hex(random_bytes(24));$cache->set($tokens[$module],['user'=>[
    'store_user_id'=>$tenant,'store_id'=>$tenant,'real_name'=>$prefix.'_'.$module,'user_name'=>$prefix,'is_super'=>1],'store_id'=>$tenant,'is_login'=>true],1800);}
$cases=[];$boundaryFindings=[];$staffMap=[];$staffIds=[];$error=null;$privateResponses=[];$replaceProof=[];
file_put_contents($runtime.'/queries.jsonl','');file_put_contents($runtime.'/serve-captures.jsonl','');
$shape=static function($value)use(&$shape){if(is_object($value)){$out=[];foreach(get_object_vars($value) as $k=>$v)$out[$k]=$shape($v);ksort($out);return ['object'=>$out];}
    if(is_array($value))return ['array'=>array_map($shape,$value)];if(is_int($value)||is_float($value))return 'number';return gettype($value);};
$deep=static function($actual,$expected,string $path='$')use(&$deep,$check):void{if(is_array($expected)){
    $check(is_array($actual),'Expected container '.$path);$ak=array_keys($actual);$ek=array_keys($expected);sort($ak);sort($ek);$check($ak===$ek,'Full keys '.$path);foreach($expected as $k=>$v)$deep($actual[$k],$v,$path.'.'.$k);
}else $check($actual===$expected,'Value/type '.$path);};
$request=static function(string $module,string $scenario,string $action,array $body,string $encoding='json',string $method='POST')use($base,$tokens,$shape,&$privateResponses):array{
    $scenario='PILOT_HAB_'.strtoupper($module).'_'.$scenario;$url=$base.'/'.$module.'/store.Habit/'.$action;
    if($method==='GET')$url.='?'.http_build_query($body);
    $headers='Host: '.$module.".habit-test.invalid\r\nAccess-Token: ".$tokens[$module]."\r\nPlatform: POC\r\nX-Pilot-Scenario: ".$scenario."\r\nContent-Type: ".($encoding==='json'?'application/json':'application/x-www-form-urlencoded')."\r\n";
    $raw=file_get_contents($url,false,stream_context_create(['http'=>['method'=>$method,'header'=>$headers,
        'content'=>$method==='GET'?'':($encoding==='json'?json_encode($body,JSON_UNESCAPED_UNICODE):http_build_query($body)),'ignore_errors'=>true,'timeout'=>30]]));
    $checkBody=is_string($raw)&&strlen($raw)<1048576;if(!$checkBody)throw new RuntimeException('Wire size/transport '.$scenario);
    preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$m);$parsed=json_decode($raw,true);if(!is_array($parsed))throw new RuntimeException('Non JSON '.$scenario);
    $privateResponses[$scenario]=['http_status'=>(int)($m[1]??0),'body'=>$parsed];
    return ['scenario'=>$scenario,'method'=>$method,'path'=>'/'.$module.'/store.Habit/'.$action,'encoding'=>$encoding,'http_status'=>(int)($m[1]??0),
        'body'=>$parsed,'raw_json_shape'=>$shape(json_decode($raw))];
};
$expect=static function(array $case,int $status=200,string $message='')use($check):void{$check($case['http_status']===200&&$case['body']['status']===$status,'Business failure '.$case['scenario']);if($message!=='')$check($case['body']['message']===$message,'Business message '.$case['scenario']);};
$habitRead=static function(string $module,string $scenario,int $id,int $selected)use($request,$expect,$query,$dm,$deep,&$cases):void{
    $case=$request($module,$scenario,'detail',['staffId'=>$id]);$expect($case);
    $rows=$query('SELECT enum_key,enum_name,people,ratio FROM yoshop_config_enum WHERE enum_type=2 AND is_delete=0 ORDER BY '.($dm?'"ORDER"':'`order`'));
    foreach($rows as &$row){$row['ratio']=$row['ratio']===null?null:bcadd((string)$row['ratio'],'0',2);$row['checked']=$selected==(int)$row['enum_key']?1:0;}unset($row);
    $deep($case['body']['data'],['detail'=>$rows]);$case['enum_original_projection_equal']=true;$cases[]=$case;
};
$rpeRows=static function(int $id,int $tenant)use($query):array{return $query('SELECT * FROM yoshop_habit_rpe WHERE staff_id=? AND store_id=? AND is_delete=0 ORDER BY train_date,id',[$id,$tenant]);};
$infoRows=static function(int $rpe,int $tenant)use($query):array{return $query('SELECT * FROM yoshop_habit_rpe_info WHERE rpe_id=? AND store_id=? AND is_delete=0 ORDER BY id',[$rpe,$tenant]);};
$project=static function(array $row,bool $info=false):array{
    foreach(['is_delete','create_by','create_time','update_time'] as $key)unset($row[$key]);
    foreach($row as $key=>&$value)if(in_array($key,['id','staff_id','train_temperature','train_environment','store_id','rpe_id','train_min','rest_min','strength'],true))$value=(int)$value;
    unset($value);return $row;
};
$rpeRead=static function(string $module,string $scenario,int $id,string $start,string $end)use($request,$expect,$query,$tenants,$project,$deep,&$cases):void{
    $body=['staffId'=>$id];if($module==='store')$body['train_date']=$start;else{$body['start_date']=$start;$body['end_date']=$end;}
    $case=$request($module,$scenario,'detailRpe',$body);$expect($case);
    $rows=$query('SELECT * FROM yoshop_habit_rpe WHERE staff_id=? AND store_id=? AND train_date>=? AND train_date<=? AND is_delete=0 ORDER BY train_date,id',[$id,$tenants[$module],$start,$module==='store'?$start:$end]);
    if(!$rows)$expected=$module==='store'?['detail'=>null]:['date_list'=>[],'detail'=>[]];
    else{
        $row=$module==='store'?$rows[0]:$rows[count($rows)-1];$infos=$query('SELECT * FROM yoshop_habit_rpe_info WHERE rpe_id=? AND store_id=? AND is_delete=0 ORDER BY id',[$row['id'],$tenants[$module]]);$pi=array_map($project,$infos);
        if($module==='store'){$detail=$project($row);$detail['info']=$pi;$expected=['detail'=>$detail];}
        else $expected=['date_list'=>array_column($rows,'train_date'),'detail'=>['staff_id'=>(int)$row['staff_id'],'train_date'=>$row['train_date'],
            'train_temperature'=>(int)$row['train_temperature'],'train_environment'=>(int)$row['train_environment'],'info'=>$pi]];
    }
    $deep($case['body']['data'],$expected);$case['full_original_projection_equal']=true;
    if($rows){$detail=&$case['body']['data']['detail'];$dateIndex=$detail['train_date']==='2030-02-02'?1:2;
        $detail['staff_id']=1;if(isset($detail['id']))$detail['id']=$dateIndex;
        foreach($detail['info'] as $idx=>&$info){$info['id']=$idx+1;$info['rpe_id']=$dateIndex;}unset($info,$detail);
    }
    $cases[]=$case;
};
try{
foreach($tenants as $module=>$tenant){
    $name=$prefix.'_'.$module.'_STAFF';$operator=$prefix.'_'.$module;
    $staff=$db->prepare('INSERT INTO yoshop_staff (name,gender,birthday,height,weight,staff_type,nation_id,career,education,marital,phone,sport_id,sport_event_id,user_id,store_id,is_delete,create_by,create_time,update_time) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
    $staff->execute([$name,1,'1990-01-01','170.00','60.00',1,0,0,0,1,'',0,0,0,$tenant,0,$operator,'2021-01-01 00:00:00','2021-01-01 00:00:00']);
    $s=$query('SELECT id FROM yoshop_staff WHERE name=? AND store_id=?',[$name,$tenant]);$check(count($s)===1&&(int)$s[0]['id']>0,'Owned synthetic staff');$id=(int)$s[0]['id'];$staffIds[$module]=$id;$staffMap[(string)$id]=$tenant;
    file_put_contents($runtime.'/private-staff-map.json',json_encode($staffMap));chmod($runtime.'/private-staff-map.json',0600);
    $habitRead($module,'DETAIL_DEFAULT',$id,$module==='store'?2:0);
    foreach([['SET_HABIT_INSERT',3],['SET_HABIT_UPDATE',4],['SET_HABIT_REPEAT',4]] as $step){
        $case=$request($module,$step[0],'setHabit',['staffId'=>$id,'habit'=>$step[1]]);$expect($case);
        $h=$query('SELECT * FROM yoshop_habit WHERE staff_id=? AND store_id=?',[$id,$tenant]);
        $check(count($h)===1&&(int)$h[0]['habit']===$step[1]&&$h[0]['create_by']===$operator,'Habit insert/update ownership');
        $case['independent_state']=['active_rows'=>1,'habit'=>$step[1],'owner_creator_equal'=>true];$cases[]=$case;
        if($step[0]==='SET_HABIT_INSERT')$habitRead($module,'DETAIL_SELECTED3',$id,3);
    }
    $habitRead($module,'DETAIL_SELECTED4',$id,4);
    $rpeRead($module,'RPE_EMPTY',$id,'2030-02-02','2030-02-03');
    $case=$request($module,'RPE_MISSING_REJECT','setRpe',['staffId'=>$id,'form'=>[]]);$expect($case,500,'开日日期不能为空');$check(!$rpeRows($id,$tenant),'Missing RPE wrote row');$case['owned_rpe_rows']=0;$cases[]=$case;
    $form=['start_date'=>'2030-02-02','end_date'=>'2030-02-03','train_temperature'=>1,'train_environment'=>1,'info'=>[
        ['start_time'=>'08:00:00','end_time'=>'09:00:00','train_min'=>60,'rest_min'=>10,'strength'=>2],
        ['start_time'=>'10:00:00','end_time'=>'11:00:00','train_min'=>60,'rest_min'=>10,'strength'=>3]]];
    $case=$request($module,'RPE_RANGE_INSERT','setRpe',['staffId'=>$id,'form'=>$form]);$expect($case);
    $rpes=$rpeRows($id,$tenant);$check(count($rpes)===2&&array_column($rpes,'train_date')===['2030-02-02','2030-02-03'],'Two future RPE dates');
    foreach($rpes as $rpe){$infos=$infoRows((int)$rpe['id'],$tenant);$check(count($infos)===2&&$rpe['create_by']===$operator,'RPE info creation');
        foreach($infos as $i=>$info){$check($info['start_time']===$form['info'][$i]['start_time']&&$info['end_time']===$form['info'][$i]['end_time']
            &&(int)$info['train_min']===60&&(int)$info['rest_min']===10&&(int)$info['strength']===$form['info'][$i]['strength']&&$info['create_by']===$operator,'Original time/minute/strength/creator');}}
    $case['independent_state']=['rpe_dates'=>['2030-02-02','2030-02-03'],'rpe_rows'=>2,'info_rows'=>4,'all_time_integer_fields_equal'=>true];$cases[]=$case;
    $rpeRead($module,'RPE_TWO_DATES',$id,'2030-02-02','2030-02-03');
    $stateBefore=[$rpes,[]];foreach($rpes as $rpe)$stateBefore[1][]=$infoRows((int)$rpe['id'],$tenant);
    $overlap=$form;$overlap['info'][1]['start_time']='08:30:00';
    $case=$request($module,'RPE_OVERLAP_REJECT','setRpe',['staffId'=>$id,'form'=>$overlap]);$expect($case,500,'训练时间不能交叉重复');
    $stateAfter=[$rpeRows($id,$tenant),[]];foreach($stateAfter[0] as $rpe)$stateAfter[1][]=$infoRows((int)$rpe['id'],$tenant);$check($stateBefore===$stateAfter,'Overlap changed RPE data');$case['original_rows_unchanged']=true;$cases[]=$case;
    $oldIds=array_column($infoRows((int)$rpes[0]['id'],$tenant),'id');$replace=$form;$replace['end_date']='2030-02-02';$replace['info'][0]['end_time']='08:35:00';$replace['info'][0]['train_min']=35;$replace['info'][0]['strength']=4;
    $case=$request($module,'RPE_REPLACE_FIRST','setRpe',['staffId'=>$id,'form'=>$replace]);$expect($case);
    $afterRpes=$rpeRows($id,$tenant);$check(count($afterRpes)===2&&array_column($afterRpes,'id')===array_column($rpes,'id'),'RPE duplicate parent row');
    $newInfo=$infoRows((int)$rpes[0]['id'],$tenant);$check(count($newInfo)===2&&!array_intersect($oldIds,array_column($newInfo,'id'))
        &&$newInfo[0]['end_time']==='08:35:00'&&(int)$newInfo[0]['train_min']===35&&(int)$newInfo[0]['strength']===4,'RPE replace physical IDs and payload');
    $case['independent_state']=['rpe_rows'=>2,'same_parent_ids'=>true,'first_date_info_rows'=>2,'old_info_ids_gone'=>true,'train_min'=>35,'strength'=>4];$cases[]=$case;
    $rpeRead($module,'RPE_REPLACED_FIRST',$id,'2030-02-02','2030-02-02');$rpeRead($module,'RPE_SECOND_UNCHANGED',$id,'2030-02-03','2030-02-03');
    if($module==='terminal')foreach([['GET_DETAIL','detail','GET','json'],['FORM_SET_HABIT','setHabit','POST','form']] as $edge){
        $ownedBefore=[$rpeRows($id,$tenant),$query('SELECT * FROM yoshop_habit WHERE staff_id=? AND store_id=?',[$id,$tenant])];
        $case=$request($module,$edge[0],$edge[1],['staffId'=>$id,'habit'=>3],$edge[3],$edge[2]);
        $unchanged=$ownedBefore===[$rpeRows($id,$tenant),$query('SELECT * FROM yoshop_habit WHERE staff_id=? AND store_id=?',[$id,$tenant])];
        $boundaryFindings[]=['scenario'=>$case['scenario'],'method'=>$edge[2],'path'=>$case['path'],'http_status'=>$case['http_status'],
            'business_status'=>$case['body']['status'],'strict_scalar_type_error'=>strpos($case['body']['message'],'must be of the type int, string given')!==false,
            'original_rows_unchanged'=>$unchanged,'classification'=>'shared-baseline-boundary-not-DB-positive'];
    }
}
}catch(Throwable $caught){$error=['class'=>get_class($caught),'message'=>$caught->getMessage(),'line'=>$caught->getLine()];}
// Exact owned cleanup, never Staff::setDeleteWith/Face/Queue or external model actions.
foreach($staffIds as $module=>$id){$tenant=$tenants[$module];$operator=$prefix.'_'.$module;
    $rpes=$query('SELECT id FROM yoshop_habit_rpe WHERE staff_id=? AND store_id=? AND create_by=?',[$id,$tenant,$operator]);
    foreach($rpes as $rpe){$d=$db->prepare('DELETE FROM yoshop_habit_rpe_info WHERE rpe_id=? AND store_id=? AND create_by=?');$d->execute([$rpe['id'],$tenant,$operator]);}
    foreach(['habit_rpe','habit'] as $table){$d=$db->prepare('DELETE FROM yoshop_'.$table.' WHERE staff_id=? AND store_id=? AND create_by=?');$d->execute([$id,$tenant,$operator]);}
    $d=$db->prepare('DELETE FROM yoshop_staff WHERE id=? AND store_id=? AND name=? AND create_by=?');$d->execute([$id,$tenant,$prefix.'_'.$module.'_STAFF',$operator]);}
foreach($tokens as $token)$cache->delete($token);
$captures=[];foreach(file($runtime.'/serve-captures.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line){$capture=json_decode($line,true);
    if(isset($staffMap[(string)$capture['staff_id']]))$capture['staff_id']=1;$captures[]=$capture;}
$fresh=$connect();$remaining=[];foreach($tables as $table)$remaining[$table]=(int)$fresh->query('SELECT COUNT(*) FROM yoshop_'.$table.' WHERE store_id IN (2147000421,2147000422)')->fetchColumn();
$tokensAbsent=true;foreach($tokens as $token)$tokensAbsent=$tokensAbsent&&$cache->get($token)===null;
$cleanup=['fresh_pdo_owned_rows'=>$remaining,'tokens_absent'=>$tokensAbsent,'natural_tables_and_enum_unchanged'=>$snapshot()===$before,'health_targets_unchanged'=>$health()===$healthBefore];
foreach($cases as &$case){$trace=$case['body']['trace_id']??null;$case['trace_format_valid']=is_string($trace)&&preg_match('/^[0-9a-f]{13}$/D',$trace)===1;if($case['trace_format_valid'])$case['body']['trace_id']='VALID_DYNAMIC_TRACE';}unset($case);
$events=[];foreach(file($runtime.'/queries.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line)$events[]=json_decode($line,true);
$report=['lane'=>$lane,'boundary_verified'=>true,'metadata'=>$metadata,'case_count'=>count($cases),'cases'=>$cases,'boundary_findings'=>$boundaryFindings,
    'snapshot_scan_metrics'=>$scanMetrics,
    'queue_captures'=>$captures,'real_queue_or_staff_power_executed'=>false,'cleanup'=>$cleanup,'source_before'=>$before,'error'=>$error,'events'=>$events];
file_put_contents($runtime.'/report.json',json_encode($report,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));chmod($runtime.'/report.json',0600);
file_put_contents($runtime.'/private-responses.json',json_encode($privateResponses,JSON_UNESCAPED_UNICODE));chmod($runtime.'/private-responses.json',0600);
echo json_encode(['lane'=>$lane,'cases'=>count($cases),'queue_captures'=>count($captures),'cleanup'=>$cleanup,'error'=>$error,'report'=>$runtime.'/report.json']),"\n";
exit($error!==null||array_sum($remaining)!==0||!$tokensAbsent||!$cleanup['natural_tables_and_enum_unchanged']||!$cleanup['health_targets_unchanged']?1:0);
