<?php
declare(strict_types=1);
$lane=getenv('MR3_LANE');$root=getenv('MR3_ROOT');if(!in_array($lane,['B','C'],true))exit(78);
$cfg=(static function($file){return require $file;})($root.'/application/database.php');
$connect=static function(bool $ai)use($lane,$cfg):PDO{$c=$ai?$cfg['ai_zhct']:$cfg;$values=[];foreach(['hostname','hostport','database','username','password'] as $k)$values[$k]=$c[$k]??$cfg[$k];
 if($lane==='B'){$local=(static function($file){return require $file;})('/workspace/zhct/zhctproject/store/application/config/local/database.php');$expected=$ai?$local['ai_zhct']:$local;foreach(['hostname','database','username','password'] as $k)if((string)$values[$k] !==(string)($expected[$k]??$local[$k]))throw new RuntimeException('Not code local');$dsn='mysql:host='.$values['hostname'].';port='.($values['hostport']?:3306).';dbname='.$values['database'].';charset=utf8mb4';}
 else{$expected=$ai?'AIZHCT_APP':'ZHCT_APP';$port=$ai?'5238':'5237';if($values['hostname']!=='127.0.0.1'||(string)$values['hostport']!==$port||$values['database']!==$expected)throw new RuntimeException('DM identity');$dsn='dm:host=127.0.0.1;port='.$port.';schema='.$expected;}
 $db=new PDO($dsn,$values['username'],$values['password'],[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER]);if($lane==='B'&&(strpos((string)$db->query('SELECT VERSION()')->fetchColumn(),'5.6.')!==0||$db->query('SELECT DATABASE()')->fetchColumn()!==$values['database']))throw new RuntimeException('MySQL effective identity');if($lane==='C'&&$db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn()!==$values['database'])throw new RuntimeException('DM effective schema');return $db;};
$q=static function(PDO $db,string $sql,array $args=[]):array{if(!preg_match('/^SELECT\b/i',$sql))throw new RuntimeException('SELECT only preflight');$s=$db->prepare($sql);$s->execute($args);$r=[];while($v=$s->fetch(PDO::FETCH_ASSOC)){$v=array_change_key_case($v,CASE_LOWER);foreach($v as &$x)if(is_resource($x))$x=stream_get_contents($x);unset($x);$r[]=$v;if(count($r)>3000)throw new RuntimeException('Preflight cap');}return $r;};
$out=['lane'=>$lane,'stage'=>'connect','database_writes'=>0];
try{$store=$connect(false);$ai=$connect(true);$out['both_database_identities_verified']=true;$out['stage']='synthetic-account';
 $s=$lane==='B'?'ydy_':'YDY_';$a=$lane==='B'?'yoshop_':'YOSHOP_';
 $staff=$q($store,'SELECT UUID,NAME,AI_USER_ID FROM '.$s.'staff WHERE NAME LIKE ? AND AI_USER_ID>0 ORDER BY ID',['CDXPAY%']);$chosen=null;
 $out['synthetic_staff_candidates']=count($staff);$out['candidate_checks']=[];$out['stage']='source-column-discovery';
 $out['user_columns']=['store'=>array_keys(($q($store,'SELECT * FROM '.$s.'user WHERE ID=(SELECT MIN(ID) FROM '.$s.'user)'))[0]??[]),'ai'=>array_keys(($q($ai,'SELECT * FROM '.$a.'user WHERE USER_ID=(SELECT MIN(USER_ID) FROM '.$a.'user)'))[0]??[])];$out['stage']='synthetic-account';
 foreach($staff as $candidate){if(!preg_match('/^CDXPAY[A-F0-9]{8}$/D',(string)$candidate['name']))continue;$users=$q($store,'SELECT ID,UUID,STAFF_UUID,ACCOUNT,NICKNAME,TYPE FROM '.$s.'user WHERE STAFF_UUID=? AND IS_DELETE=0',[$candidate['uuid']]);$storeMatch=count($users)===1&&$users[0]['account']===$candidate['name'];$aiUsers=$q($ai,'SELECT USER_ID,NICK_NAME FROM '.$a.'user WHERE USER_ID=?',[(int)$candidate['ai_user_id']]);$aiMatch=count($aiUsers)===1&&$aiUsers[0]['nick_name']===$candidate['name'];$out['candidate_checks'][]=['store_unique'=>count($users)===1,'store_account_marker_equal'=>$storeMatch,'ai_unique'=>count($aiUsers)===1,'ai_nickname_marker_equal'=>$aiMatch];if(!$storeMatch||!$aiMatch)continue;$chosen=['staff'=>$candidate,'store_user'=>$users[0],'ai_user'=>$aiUsers[0]];break;}
 if(!$chosen)throw new RuntimeException('No exact synthetic CDXPAY mapping');
 $frozenLease=['B'=>'f483ba1c130ac167b850ae5099196f085b2f5b7db2faf5787a201eb5f7dbf321','C'=>'8b7f1416ace012169e9ac5c94fceb49115287a7006c1f0712ebac2d30af000b4'];if(hash('sha256',$chosen['staff']['name'])!==$frozenLease[$lane])throw new RuntimeException('Frozen synthetic lease changed');
 $out['synthetic_account']=['marker_format_verified'=>true,'store_user_unique'=>true,'ai_user_unique'=>true,'names_equal'=>true,'lease_marker_sha256'=>hash('sha256',$chosen['staff']['name'])];
 $staffNow=$q($store,'SELECT IS_SHOW,JOB_STATUS,AI_USER_ID FROM '.$s.'staff WHERE UUID=?',[$chosen['staff']['uuid']]);$userNow=$q($store,'SELECT AI_USER_ID FROM '.$s.'user WHERE ID=?',[(int)$chosen['store_user']['id']]);if((int)$staffNow[0]['is_show']!==1||(int)$staffNow[0]['job_status']!==1||(int)$userNow[0]['ai_user_id']!==(int)$chosen['staff']['ai_user_id'])throw new RuntimeException('Frozen synthetic staff inactive or user mapping differs');
 $out['stage']='metadata';$tables=['meal_order','meal_order_refund','security_audit_log','restaurants','equipment','api_auth','initial_menu','user'];
 foreach($tables as $t){$name=$s.$t;if($lane==='B'){$out['schema'][$t]=$q($store,'SELECT COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION',[$name]);$out['constraints'][$t]=$q($store,'SELECT CONSTRAINT_NAME,COLUMN_NAME,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY CONSTRAINT_NAME,ORDINAL_POSITION',[$name]);$out['triggers'][$t]=$q($store,'SELECT TRIGGER_NAME,EVENT_MANIPULATION FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE=?',[$name]);}
 else{$out['schema'][$t]=$q($store,'SELECT COLUMN_NAME,DATA_TYPE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID',[strtoupper($name)]);$out['constraints'][$t]=$q($store,'SELECT CONSTRAINT_NAME,CONSTRAINT_TYPE,R_CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME=? ORDER BY CONSTRAINT_NAME',[strtoupper($name)]);$out['triggers'][$t]=$q($store,'SELECT TRIGGER_NAME,STATUS FROM USER_TRIGGERS WHERE TABLE_NAME=?',[strtoupper($name)]);}}
 foreach(['user','recharge_order','user_balance_log','user_gift_log','user_subsidy','user_subsidy_log','user_subsidy_used','account_settlement','recharge_gift_rule'] as $t){$name=$a.$t;$out['ai_schema'][$t]=$lane==='B'?$q($ai,'SELECT COLUMN_NAME,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION',[$name]):$q($ai,'SELECT COLUMN_NAME,DATA_TYPE,DATA_PRECISION,DATA_SCALE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID',[strtoupper($name)]);if(!$out['ai_schema'][$t])throw new RuntimeException('Required AI schema absent');$out['ai_triggers'][$t]=$lane==='B'?$q($ai,'SELECT TRIGGER_NAME,EVENT_MANIPULATION FROM information_schema.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE=?',[$name]):$q($ai,'SELECT TRIGGER_NAME,STATUS FROM USER_TRIGGERS WHERE TABLE_NAME=?',[strtoupper($name)]);$out['ai_constraints'][$t]=$lane==='B'?$q($ai,'SELECT CONSTRAINT_NAME,COLUMN_NAME,REFERENCED_TABLE_NAME,REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY CONSTRAINT_NAME,ORDINAL_POSITION',[$name]):$q($ai,'SELECT CONSTRAINT_NAME,CONSTRAINT_TYPE,R_CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME=? ORDER BY CONSTRAINT_NAME',[strtoupper($name)]);}
 $out['stage']='real-local-wallet-gates';require_once $root.'/env.php';define('APP_PATH',__DIR__.'/source/application/');define('RUNTIME_PATH',__DIR__.'/runtime-cli/');require_once $root.'/thinkphp/base.php';\think\App::initCommon();\think\Config::set($cfg,'database');\think\Db::clear();\think\Config::set('redis',[]);\think\Config::set(['type'=>'File','path'=>__DIR__.'/cli-cache/','prefix'=>'CENT_CLI_'],'cache');\think\Log::init(['type'=>'File','path'=>__DIR__.'/runtime-cli/safe-log/','level'=>['no_raw_application_log']]);
 $oneCard=(new \app\common\service\onecard\UserGuard())->isOneCardUserByIdentity((int)$chosen['store_user']['id'],(string)$chosen['staff']['uuid']);$consumer=new \app\api\service\Consume();$bank=new ReflectionMethod($consumer,'isZhangjiagangSettlement');$bank->setAccessible(true);$bankEnabled=$bank->invoke($consumer);if($oneCard||$bankEnabled)throw new RuntimeException('External payment path not safe');
 $rules=$q($ai,'SELECT * FROM '.$a.'recharge_gift_rule WHERE STORE_ID=10001 AND STATUS=1 AND IS_DELETE=0 AND MIN_MONEY<=?',['0.01']);$policy=new \app\common\service\account\GiftAccountPolicy();$rule=$policy->matchGiftRule($rules,'0.01','cash');$allocation=$policy->buildRechargeAllocation('0.01',$rule?(string)$rule['gift_money']:'0.00',(bool)$rule);if($allocation['gift_money']!=='0.00')throw new RuntimeException('Cent recharge would grant a gift');$out['eligibility']=['non_onecard_user'=>true,'non_bank_branch'=>true,'staff_active_and_both_user_ids_equal'=>true,'forecast_cash'=>'0.01','forecast_gift'=>'0.00','gift_rule_matched'=>(bool)$rule,'cash_only_scope'=>true];
 $out['stage']='wallet-schema';if($lane==='B')$all=$q($ai,"SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND (TABLE_NAME LIKE 'yoshop_user%' OR TABLE_NAME LIKE '%settlement%') ORDER BY TABLE_NAME");else $all=$q($ai,"SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME LIKE 'YOSHOP_USER%' OR TABLE_NAME LIKE '%SETTLEMENT%' ORDER BY TABLE_NAME");
 $out['wallet_table_candidates']=array_column($all,'table_name');$out['stage']='private-lease';$journal=__DIR__.'/private-lease.json';if(file_exists($journal)){if(json_decode((string)file_get_contents($journal),true)!==$chosen)throw new RuntimeException('Existing lease journal differs');$out['private_lease_reused']=true;}else{file_put_contents($journal,json_encode($chosen));chmod($journal,0600);$out['private_lease_written']=true;}$out['failure']=null;
}catch(Throwable $e){$out['failure']=['class'=>get_class($e),'reason'=>$e->getMessage()];}
file_put_contents(__DIR__.'/preflight-private.json',json_encode($out,JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));chmod(__DIR__.'/preflight-private.json',0600);
if(defined('RFJ_CONTEXT_ONLY')){if($out['failure']!==null)throw new RuntimeException('Preflight context failed: '.$out['failure']['reason']);return;}
echo json_encode($out,JSON_UNESCAPED_UNICODE|JSON_INVALID_UTF8_SUBSTITUTE),"\n";exit($out['failure']===null?0:2);
