<?php
declare(strict_types=1);
$lane=getenv('FR2_LANE');$root=getenv('FR2_ROOT');$out=['lane'=>$lane,'variant'=>getenv('FR2_BASELINE')==='1'?'red':'green','routes'=>[],'read_only'=>true,'database_writes'=>0];$stage='config';
function readRows(PDO $db,string $sql,array $args=[]):array{if(!preg_match('/^SELECT\b/i',$sql))throw new RuntimeException('SELECT only');$q=$db->prepare($sql);$q->execute($args);$r=[];while($v=$q->fetch(PDO::FETCH_ASSOC)){if(count($r)>2000)throw new RuntimeException('Cap');$v=array_change_key_case($v,CASE_LOWER);foreach($v as &$x)if(is_resource($x))$x=stream_get_contents($x);ksort($v);$r[]=$v;}return $r;}
function digest($x){return hash('sha256',json_encode($x,JSON_UNESCAPED_UNICODE|JSON_INVALID_UTF8_SUBSTITUTE));}
function shape($v){if(is_int($v)||is_float($v))return 'number';if(!is_array($v))return gettype($v);if(!$v)return [];if(array_keys($v)===range(0,count($v)-1))return [shape($v[0])];$out=[];foreach($v as $k=>$x)$out[$k]=shape($x);ksort($out);return $out;}
function matchesSource(array $item,array $source,string $table):bool{foreach($source as $k=>$v){if(!array_key_exists($k,$item))continue;$expected=$v;if(in_array($k,['id','log_id','user_id','order_id','scene','pay_status'],true))$expected=(int)$v;elseif($k==='money')$expected=number_format((float)$v,2,'.','');elseif($k==='create_time'&&$table==='user_balance_log')$expected=date('Y-m-d H:i:s',(int)$v);if($item[$k]!==$expected)return false;}return true;}
function request($path,$params,$uid){global $lane,$root;require_once $root.'/application/base/Security.php';$token=\app\base\Security::encrypt(implode("\t",[(string)$uid,'CDXFR2_READONLY','CDXFR2_READONLY','0','','','CDXFR2_READONLY']));$base='http://127.0.0.1:'.($lane==='B'?18301:18302);$c=stream_context_create(['http'=>['method'=>'GET','header'=>'token: '.$token."\r\nX-FR2-BASELINE: ".getenv('FR2_BASELINE')."\r\n",'ignore_errors'=>true,'timeout'=>30]]);$start=microtime(true);$raw=@file_get_contents($base.$path.'?'.http_build_query($params),false,$c);$j=json_decode((string)$raw,true);$out=['status'=>(int)substr($http_response_header[0]??'',9,3),'duration_ms'=>round((microtime(true)-$start)*1000),'json_valid'=>is_array($j)];if(is_array($j)){$out['response_sha256']=digest($j);$out['code']=$j['code']??null;$out['shape']=shape($j);if(($j['code']??-1)!==0)$out['message_sha12']=substr(hash('sha256',(string)($j['message']??$j['msg']??'')),0,12);}return [$out,$j];}
try{
 if(!in_array($lane,['B','C'],true))throw new RuntimeException('Lane');$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(($cfg[$k]??null)!==($local[$k]??null))throw new RuntimeException('Not local');$dsn='mysql:host='.$cfg['hostname'].';port='.($cfg['hostport']?:3306).';dbname='.$cfg['database'].';charset=utf8mb4';}
 else{foreach(['hostname'=>'POC_HOST','hostport'=>'POC_PORT','database'=>'POC_SCHEMA','username'=>'POC_USER','password'=>'POC_PASSWORD'] as $k=>$e)if((string)$cfg[$k]!==getenv($e))throw new RuntimeException('DM config');if(getenv('POC_HOST')!=='127.0.0.1'||getenv('POC_PORT')!=='5237'||getenv('POC_SCHEMA')!=='ZHCT_APP')throw new RuntimeException('DM identity');$dsn='dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';}
 $out['original_debug']=$cfg['debug']??null;$db=new PDO($dsn,$cfg['username'],$cfg['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);$prefix=$lane==='B'?'ydy_':'YDY_';
 $stage='source';$tables=['user_balance_log'=>'log_id','cashout_order'=>'id'];$sources=[];
 foreach($tables as $table=>$pk){$full=$prefix.$table;$countSql=$lane==='B'?'SELECT COUNT(*) AS N FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=?':'SELECT COUNT(*) AS N FROM USER_TABLES WHERE TABLE_NAME=?';$exists=(int)readRows($db,$countSql,[$full])[0]['n']===1;$out['preflight'][$table]=['exists'=>$exists];if(!$exists)continue;$n=(int)readRows($db,'SELECT COUNT(*) AS N FROM '.$full)[0]['n'];$out['preflight'][$table]['rows']=$n;$row=$n?readRows($db,'SELECT * FROM '.$full.' WHERE '.$pk.'=(SELECT MAX('.$pk.') FROM '.$full.')')[0]:null;$uid=$row?(int)$row['user_id']:1;$sources[$table]=['row'=>$row,'uid'=>$uid,'owned'=>readRows($db,'SELECT * FROM '.$full.' WHERE USER_ID=?',[$uid])];$out['preflight'][$table]['selected_nonempty']=$row!==null;}
 foreach($sources as $table=>$source){$out['preflight'][$table]['money_php_type']=$source['row']===null?null:gettype($source['row']['money']);$metaSql=$lane==='B'?'SELECT NUMERIC_PRECISION AS P,NUMERIC_SCALE AS S FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND COLUMN_NAME=?':'SELECT DATA_PRECISION AS P,DATA_SCALE AS S FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? AND COLUMN_NAME=?';$m=readRows($db,$metaSql,[$prefix.$table,$lane==='B'?'money':'MONEY']);$out['preflight'][$table]['money_precision_scale']=$m?[(int)$m[0]['p'],(int)$m[0]['s']]:null;}
 $out['source_before']=digest($sources);
 $log=$sources['user_balance_log']['row']??null;$uid=$sources['user_balance_log']['uid']??1;$time=$log?(int)$log['create_time']:strtotime('2026-10-01');$params=['year'=>date('Y',$time),'month'=>date('m',$time),'scene'=>0,'page'=>1];
 foreach(['balance_list'=>['/api/balance.log/list',$params,$uid],'balance_detail'=>['/api/balance.log/detail',['logId'=>$log?(int)$log['log_id']:0],$uid],'balance_detail_missing'=>['/api/balance.log/detail',['logId'=>0],$uid],'cashout_list'=>['/api/cashout/list',['page'=>1],$sources['cashout_order']['uid']??1]] as $name=>$scenario){$stage=$name;[$r,$j]=request(...$scenario);$r['method']='GET';$r['path']=$scenario[0];
 if(is_array($j)&&($j['code']??-1)===0&&in_array($name,['balance_list','cashout_list'],true)){$d=$j['data'];$items=$d['data']??[];$r['response_rows']=count($items);$r['source_match']=true;$r['ordered_desc']=true;$last=null;foreach($items as $item){$table=$name==='balance_list'?'user_balance_log':'cashout_order';$pk=$tables[$table];$source=readRows($db,'SELECT * FROM '.$prefix.$table.' WHERE '.$pk.'=? AND USER_ID=?',[(int)$item[$pk],$scenario[2]]);if(count($source)!==1){$r['source_match']=false;continue;}$v=$source[0];$r['source_match']=$r['source_match']&&matchesSource($item,$v,$table);$now=$v['create_time'];if($last!==null&&$now>$last)$r['ordered_desc']=false;$last=$now;} }
 elseif(is_array($j)&&($j['code']??-1)===0&&$name==='balance_detail'&&$log){$d=$j['data'];$r['source_match']=(int)($d['log_id']??0)===(int)$log['log_id']&&matchesSource($d,$log,'user_balance_log');}
 $out['routes'][$name]=$r;
 }
 $after=[];foreach($sources as $table=>$source)$after[$table]=['row'=>$source['row'],'uid'=>$source['uid'],'owned'=>readRows($db,'SELECT * FROM '.$prefix.$table.' WHERE USER_ID=?',[$source['uid']])];$out['source_unchanged']=digest($after)===$out['source_before'];$out['failure']='';
}catch(Throwable $e){$out['failure']=['stage'=>$stage,'class'=>get_class($e),'message_sha12'=>substr(hash('sha256',$e->getMessage()),0,12)];}
echo json_encode($out,JSON_UNESCAPED_UNICODE|JSON_INVALID_UTF8_SUBSTITUTE),"\n";exit(empty($out['failure'])?0:2);
