<?php
declare(strict_types=1);
set_exception_handler(static function(Throwable $e):void{$message=$e->getMessage();$diagnostic=[];if($message==='Not code local'){foreach(['hostname','database','username','password','hostport']as $k)$diagnostic[$k]=((string)($GLOBALS['cfg'][$k]??''))===((string)($GLOBALS['local'][$k]??''));$diagnostic['effective_hostport_equal']=(string)(($GLOBALS['cfg']['hostport']??'')?:3306)===(string)(($GLOBALS['local']['hostport']??'')?:3306);}echo json_encode(['scope_diagnostic'=>$diagnostic,'safe'=>false,'class'=>get_class($e),'reason'=>in_array($message,['Not code local','DM scope'],true)?$message:'preflight failed','reason_sha256'=>hash('sha256',$message)]),"\n";exit(2);});
$lane=getenv('MR3_LANE');$root=getenv('MR3_ROOT');$cfg=(static function($f){return require $f;})($root.'/application/database.php');
if($lane==='B'){$local=(static function($f){return require $f;})('/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((string)($cfg['hostport']?:3306)!==(string)($local['hostport']?:3306))throw new RuntimeException('Not code local');$dsn='mysql:host='.$cfg['hostname'].';port='.($cfg['hostport']?:3306).';dbname='.$cfg['database'].';charset=utf8mb4';}else{if($cfg['hostname']!=='127.0.0.1'||(string)$cfg['hostport']!=='5237'||$cfg['database']!=='ZHCT_APP')throw new RuntimeException('DM scope');$dsn='dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';}
$options=[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER];if($lane==='B'){$options[PDO::ATTR_EMULATE_PREPARES]=false;$options[PDO::ATTR_STRINGIFY_FETCHES]=false;}$db=new PDO($dsn,$cfg['username'],$cfg['password'],$options);$p=$lane==='B'?'ydy_':'YDY_';$q=static function(string $sql,array $args=[])use($db):array{$s=$db->prepare($sql);$s->execute($args);$rows=[];while($r=$s->fetch(PDO::FETCH_ASSOC)){$r=array_change_key_case($r,CASE_LOWER);foreach($r as &$v)if(is_resource($v))$v=stream_get_contents($v);unset($v);$rows[]=$r;if(count($rows)>10000)throw new RuntimeException('Bounded result cap');}return $rows;};$check=static function(bool $ok,string $why):void{if(!$ok)throw new RuntimeException($why);};$hash=static function($v):string{return hash('sha256',serialize($v));};$report=['lane'=>$lane,'business_dml'=>0,'ddl'=>0,'safe'=>false,'tables'=>[],'failure'=>null];$private=[];
try{if($lane==='B')$check(strpos((string)$db->query('SELECT VERSION()')->fetchColumn(),'5.6.')===0,'MySQL5.6');else $check($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()==='ZHCT_APP','DM identity');
 foreach(['billing_record','billing_record_detail','billing_record_log','staff_department']as $key){$table=$p.$key;$entry=['safe'=>false];try{$entry['columns']=$lane==='B'?$q('SELECT COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA,DATETIME_PRECISION FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION',[$table]):$q('SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID',[strtoupper($table)]);$check($entry['columns']!==[],'Table absent');if($key==='staff_department'){$entry['safe']=true;$entry['source_audit']='metadata-only; no department filter in frozen requests';$report['tables'][$key]=$entry;continue;}$rows=$q('SELECT * FROM '.$table.' ORDER BY ID');$private[$key]=$rows;$entry['rows']=count($rows);$entry['sha256']=$hash($rows);$entry['safe']=true;$entry['failure']=null;}catch(Throwable $e){$entry['failure']=['class'=>get_class($e),'reason'=>$e->getMessage()];}$report['tables'][$key]=$entry;}
 $report['safe']=$report['tables']['billing_record']['safe'];if($report['safe']){$private['bills_in_scope']=array_values(array_filter($private['billing_record'],static function($r){return (int)$r['store_id']===10001&&(int)$r['is_delete']===0;}));usort($private['bills_in_scope'],static function($a,$b){return (int)$b['id']<=>(int)$a['id'];});$report['in_scope_bills']=count($private['bills_in_scope']);$report['in_scope_status_counts']=[];foreach($private['bills_in_scope']as $r){$key=(string)$r['status'];$report['in_scope_status_counts'][$key]=($report['in_scope_status_counts'][$key]??0)+1;}
  $private['money_text']=[];foreach(['billing_record'=>['amount','original_amount'],'billing_record_detail'=>['original_amount','adjusted_amount','raw_pay_price','raw_refund_amount','raw_amount']]as $table=>$fields){if(!$report['tables'][$table]['safe'])continue;$cols=array_map('strtolower',array_column($report['tables'][$table]['columns'],'column_name'));$select=['ID'];foreach($fields as $f)if(in_array($f,$cols,true))$select[]='CAST('.$f.' AS '.($lane==='B'?'CHAR':'VARCHAR').'(64)) AS CDX_'.$f;$private['money_text'][$table]=$q('SELECT '.implode(',',$select).' FROM '.$p.$table.' ORDER BY ID');}
  $private['admin']=$q('SELECT ID,UUID FROM '.$p.'user WHERE ID=(SELECT MIN(ID) FROM '.$p.'user WHERE TYPE=0 AND IS_DELETE=0)')[0];
 }
 $report['original_debug']=$cfg['debug']??null;
}catch(Throwable $e){$report['failure']=['class'=>get_class($e),'reason'=>$e->getMessage()];}
foreach(['private-source.json'=>$private,'private-preflight.json'=>$report]as $f=>$v){file_put_contents(__DIR__.'/'.$f,json_encode($v,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));chmod(__DIR__.'/'.$f,0600);}if(defined('CDX_BILL_CONTEXT')){if(!$report['safe'])throw new RuntimeException('Billing source unavailable');return;}echo json_encode($report),"\n";exit($report['safe']?0:2);
