<?php
declare(strict_types=1);
// Safe deterministic-input read sweep; this is NOT whole-interface acceptance.
date_default_timezone_set('Asia/Shanghai');
$lane=(string)getenv('PILOT_LANE'); $dialect=(string)getenv('POC_DIALECT');
$base=(string)getenv('POC_BASE_URL'); $runtime=(string)getenv('PILOT_RUNTIME');
if (!in_array($lane,['B','C'],true) || !preg_match('#^http://127\.0\.0\.1:1822[12]$#',$base)) exit(78);
$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);
if (empty($boundary['external_stubs']) || empty($boundary['readonly_get_allowlist'])) throw new RuntimeException('Boundary failed');
$denied=file_get_contents($base.'/api/sport.item/record',false,stream_context_create(['http'=>[
    'method'=>'POST','ignore_errors'=>true,'timeout'=>5]]));
if (strpos($http_response_header[0] ?? '', '403') === false) throw new RuntimeException('Write route not denied');
$dsn=$dialect==='dm' ? 'dm:host='.getenv('POC_AI_HOST').';port='.getenv('POC_AI_PORT').';schema='.getenv('POC_AI_SCHEMA')
    : 'mysql:host='.getenv('POC_AI_HOST').';port='.getenv('POC_AI_PORT').';dbname='.getenv('POC_AI_SCHEMA').';charset=utf8';
$pdo=new PDO($dsn,getenv('POC_AI_USER'),getenv('POC_AI_PASSWORD'),[
    PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);
file_put_contents($runtime.'/ai_queries.jsonl','');chmod($runtime.'/ai_queries.jsonl',0600);
$table=static function(string $name)use($dialect):string{return $dialect==='dm'?strtoupper('yoshop_'.$name):'yoshop_'.$name;};
$query=static function(string $sql,array $args=[])use($pdo):array{
    $statement=$pdo->prepare($sql);$statement->execute($args);$rows=$statement->fetchAll(PDO::FETCH_ASSOC);
    foreach($rows as &$row)foreach($row as &$value)if(is_resource($value))$value=stream_get_contents($value);
    return $rows;
};
$canonical=static function($value)use(&$canonical){
    if(is_array($value)){if(array_keys($value)!==range(0,count($value)-1))ksort($value);
        foreach($value as &$part)$part=$canonical($part);unset($part);}return $value;};
$digest=static function($value)use($canonical):string{return hash('sha256',json_encode($canonical($value),JSON_UNESCAPED_UNICODE));};
$shape=static function($value)use(&$shape){if(!is_array($value))return gettype($value);
    if(array_keys($value)===range(0,count($value)-1))return ['array_unique_item_shapes'=>array_values(array_unique(array_map(static function($part)use(&$shape){return json_encode($shape($part));},$value)))];
    $out=[];foreach($value as $key=>$part)$out[$key]=$shape($part);ksort($out);return $out;};
$snapshot=static function()use($query,$table,$digest):array{
    $out=[];foreach(['config_enum','sport_item','sport_cate'] as $name){
        $rows=$query('SELECT * FROM '.$table($name).' ORDER BY ID');$out[$name]=['rows'=>count($rows),'hash'=>$digest($rows)];}
    return $out;
};
$before=$snapshot(); $cases=[]; $responses=[];
$request=static function(string $scenario,string $path,array $args)use($base,$digest,$shape,&$responses):array{
    $raw=file_get_contents($base.$path.'?'.http_build_query(['storeId'=>10001]+$args),false,
        stream_context_create(['http'=>['timeout'=>20,'ignore_errors'=>true,
            'header'=>"Platform: POC\r\nX-Pilot-Scenario: ".$scenario."\r\n"]]));
    preg_match('#^HTTP/\S+\s+(\d+)#',$http_response_header[0]??'',$match);
    $body=json_decode((string)$raw,true);$list=$body['data']['list']??null;
    $responses[$scenario]=['http_status'=>(int)($match[1]??0),'body'=>$body];
    $result=['scenario'=>$scenario,'path'=>$path,'args'=>$args,'http_status'=>(int)($match[1]??0),
        'business_status'=>$body['status']??null,'response_shape'=>$shape($body),'rows'=>is_array($list)?count($list):null,
        'trace_format_valid'=>is_string($body['trace_id']??null)&&preg_match('/^[0-9a-f]{13}$/D',$body['trace_id'])===1];
    if(is_array($body))unset($body['trace_id']);
    $result['response_hash_without_trace']=$digest($body);
    return [$result,$list];
};
$matchRows=static function(array $list,array $source,string $key):bool{
    $map=[];foreach($source as $row)$map[(string)$row[$key]]=$row;
    if(count($list)!==count($source))return false;
    foreach($list as $row){$original=$map[(string)($row[$key]??'')]??null;if($original===null)return false;
        foreach($row as $field=>$value){if($field==='consume'){if($value!=='')return false;continue;}
            if(!array_key_exists($field,$original))return false;$expected=$original[$field];
            if($field==='create_time'&&is_numeric($expected))$expected=date('Y-m-d H:i:s',(int)$expected);
            if(is_numeric($value)&&is_numeric($expected)){if(bccomp((string)$value,(string)$expected,10)!==0)return false;}
            elseif($value!==$expected)return false;}}
    return true;
};
foreach(range(1,17) as $type){
    [$result,$list]=$request('PILOT_AI_ENUM_'.$type,'/api/enum/all',['enumType'=>$type]);
    $source=$query('SELECT * FROM '.$table('config_enum').' WHERE ENUM_TYPE=? AND IS_DELETE=0 ORDER BY '.($dialect==='dm'?'"ORDER"':'`order`'),[$type]);
    $result['source_rows']=count($source);$result['source_projection_equal']=is_array($list)&&$matchRows($list,$source,'enum_key');
    $result['source_hash']=$digest($source);$cases[]=$result;
}
$source=$query('SELECT * FROM '.$table('sport_item').' WHERE STORE_ID=10001 AND IS_DELETE=0 ORDER BY ID');
$cates=$query('SELECT * FROM '.$table('sport_cate').' WHERE STORE_ID=10001 AND IS_DELETE=0 ORDER BY ID');
$cateIds=array_slice(array_values(array_unique(array_column($source,'cate_id'))),0,2);
foreach(array_merge([null],$cateIds) as $cate){
    $args=$cate===null?[]:['cate_id'=>(int)$cate];
    [$result,$list]=$request('PILOT_AI_SPORT_ALL_'.($cate===null?'ALL':(int)$cate),'/api/sport.item/all',$args);
    $expected=$cate===null?$source:array_values(array_filter($source,static function(array $row)use($cate):bool{return (int)$row['cate_id']===(int)$cate;}));
    $result['source_rows']=count($expected);$result['source_projection_equal']=is_array($list)&&$matchRows($list,$expected,'id');
    $result['source_hash']=$digest($expected);$cases[]=$result;
}
[$result,$list]=$request('PILOT_AI_SPORT_CATE','/api/sport.cate/all',[]);
$result['source_rows']=count($cates);$result['source_projection_equal']=is_array($list)&&$matchRows($list,$cates,'id');
$result['source_hash']=$digest($cates);$cases[]=$result;
[$result,$list]=$request('PILOT_AI_SPORT_GROUPED','/api/sport.item/grouped',[]);
$expected=[];foreach($cates as $cate){$items=[];foreach($source as $item)if((int)$item['cate_id']===(int)$cate['id'])$items[]=[
    'id'=>(int)$item['id'],'name'=>(string)$item['name'],'img'=>(string)($item['img']??''),
    'intensity'=>isset($item['intensity'])?(float)$item['intensity']:0.0,'consume'=>''];
    $expected[]=['cate_id'=>(int)$cate['id'],'cate_name'=>(string)$cate['name'],'items'=>$items];}
$result['source_rows']=count($cates);$result['source_projection_equal']=$digest($list)===$digest($expected);
$result['source_hash']=$digest($expected);$cases[]=$result;
$after=$snapshot();$events=[];$fingerprints=[];$sites=[];$nonRead=[];
$classification=['connection_events'=>0,'metadata_events'=>0,'unclassified_events'=>0];
foreach(file($runtime.'/ai_queries.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES)?:[] as $line){
    $event=json_decode($line,true);$events[]=$event;
    if(($event['kind']??'')==='listener_classification'){
        foreach($classification as $key=>&$value)$value+=$event[$key]??0;unset($value);continue;}
    if(($event['kind']??'')!=='query_observed')continue;
    $fingerprints[$event['fingerprint']]=true;foreach($event['callsites'] as $site)$sites[$site]=true;
    if(in_array($event['operation'],['insert','update','delete','replace','merge'],true))$nonRead[]=$event;
}
$failures=array_values(array_filter($cases,static function(array $case):bool{
    return $case['http_status']!==200||$case['business_status']!==200||!$case['source_projection_equal'];}));
$output=['lane'=>$lane,'readonly_db_sweep'=>true,'boundary_verified'=>true,'cases'=>$cases,
    'case_count'=>count($cases),'case_source_failures'=>count($failures),'before_after_unchanged'=>$before===$after,
    'source_snapshot'=>$after,'observed_queries'=>count(array_filter($events,static function(array $e):bool{return ($e['kind']??'')==='query_observed';})),
    'unique_fingerprints'=>count($fingerprints),'callsites'=>array_keys($sites),'non_select_observed'=>count($nonRead),
    'listener_classification'=>$classification,'events'=>$events,'acceptance'=>'partial_read_paths_only_not_whole_interface_pass'];
file_put_contents($runtime.'/ai_report.json',json_encode($output,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE));
chmod($runtime.'/ai_report.json',0600);
// Public enum/exercise catalog only, private runtime only; never copy these bodies into Git.
file_put_contents($runtime.'/ai_responses.json',json_encode($responses,JSON_UNESCAPED_UNICODE));
chmod($runtime.'/ai_responses.json',0600);
echo json_encode(['lane'=>$lane,'cases'=>count($cases),'source_failures'=>count($failures),
    'unchanged'=>$before===$after,'queries'=>$output['observed_queries'],'fingerprints'=>count($fingerprints),
    'callsites'=>count($sites),'non_select'=>count($nonRead),'report'=>$runtime.'/ai_report.json']),"\n";
exit(count($failures)>0||$before!==$after||count($nonRead)>0||count($sites)===0?1:0);
