<?php
declare(strict_types=1);
$lane=getenv('MR3_LANE');$root=getenv('MR3_ROOT');$out=['lane'=>$lane,'routes'=>[],'database_writes'=>0];$stage='config';
function rr(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 dh($v){return hash('sha256',json_encode($v,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])];$r=[];foreach($v as $k=>$x)$r[$k]=shape($x);ksort($r);return $r;}
function wireShape($v){if(is_object($v)){$fields=[];foreach(get_object_vars($v) as $k=>$x)$fields[$k]=wireShape($x);ksort($fields);return ['kind'=>'object','fields'=>$fields];}if(is_array($v)){$shapes=[];foreach($v as $x){$s=wireShape($x);$shapes[dh($s)]=$s;}ksort($shapes);return ['kind'=>'array','element_shapes'=>array_values($shapes)];}return is_int($v)||is_float($v)?'number':gettype($v);}
if(($argv[1]??'')==='--wire-shape-selftest'){$checks=[wireShape(json_decode('{}'))!==wireShape(json_decode('[]')),wireShape(json_decode('{"a":[],"b":{}}'))!==wireShape(json_decode('{"a":{},"b":[]}')),wireShape(json_decode('{"a":1,"b":"x"}'))===wireShape(json_decode('{"b":"y","a":2}')),wireShape(1)===wireShape(1.5),wireShape('1')!==wireShape(1),count(wireShape(json_decode('[{},[],{"a":1}]'))['element_shapes'])===3,wireShape(null)==='NULL'];if(in_array(false,$checks,true))exit(2);echo json_encode(['wire_shape_selftest_pass'=>count($checks)]),"\n";exit;}
function rq(string $method,string $path,array $params,array $actor):array{global $lane,$root;require_once $root.'/application/base/Security.php';$token=\app\base\Security::encrypt(implode("\t",[(string)$actor['id'],(string)$actor['uuid'],'CDXMR3_READONLY',(string)$actor['type'],'','','CDXMR3_READONLY']));$base='http://127.0.0.1:'.($lane==='B'?18301:18302);$h='token: '.$token."\r\n";$args=['method'=>$method,'header'=>$h,'ignore_errors'=>true,'timeout'=>30];if($method==='GET')$path.='?'.http_build_query($params);else{$args['header'].="Content-Type: application/x-www-form-urlencoded\r\n";$args['content']=http_build_query($params);}$t=microtime(true);$raw=@file_get_contents($base.$path,false,stream_context_create(['http'=>$args]));$j=json_decode((string)$raw,true);$r=['method'=>$method,'path'=>strtok($path,'?'),'status'=>(int)substr($http_response_header[0]??'',9,3),'duration_ms'=>round((microtime(true)-$t)*1000),'json_valid'=>is_array($j)];if(is_array($j)){$r['code']=$j['code']??null;$r['shape']=shape($j);$r['response_sha256']=dh($j);$r['wire_response_sha256']=hash('sha256',(string)$raw);$r['wire_shape']=wireShape(json_decode((string)$raw));if(($j['code']??-1)!==0)$r['message_sha12']=substr(hash('sha256',(string)($j['message']??$j['msg']??'')),0,12);}return [$r,$j,json_decode((string)$raw)];}
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]);$p=$lane==='B'?'ydy_':'YDY_';$stage='source';
 foreach(['meal_order','initial_menu'] as $table){$sql=$lane==='B'?'SELECT COLUMN_NAME,DATA_TYPE,NUMERIC_PRECISION AS PRECISION_VALUE,NUMERIC_SCALE AS SCALE_VALUE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION':'SELECT COLUMN_NAME,DATA_TYPE,DATA_PRECISION AS PRECISION_VALUE,DATA_SCALE AS SCALE_VALUE FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID';$out['schema'][$table]=rr($db,$sql,[$p.$table]);}
 $stage='source-admin';$admin=rr($db,'SELECT ID,UUID,TYPE FROM '.$p.'user WHERE ID=(SELECT MIN(ID) FROM '.$p.'user WHERE TYPE=0 AND IS_DELETE=0)');if(count($admin)!==1)throw new RuntimeException('Natural admin absent');$admin=$admin[0];
 $parts=[];foreach(['areas'=>'restaurant_area','shops'=>'restaurant_shop','stalls'=>'restaurants'] as $key=>$table){try{$parts[$key]=rr($db,'SELECT * FROM '.$p.$table.' ORDER BY ID ASC');}catch(Throwable $e){$parts[$key]=[];$out['source_limits'][$key]=['class'=>get_class($e),'message_sha12'=>substr(hash('sha256',$e->getMessage()),0,12)];}}$areas=$parts['areas'];$shops=$parts['shops'];$stalls=$parts['stalls'];
 $stage='source-order';$order=rr($db,'SELECT * FROM '.$p.'meal_order WHERE ID=(SELECT MAX(ID) FROM '.$p.'meal_order WHERE IS_DELETE=0 AND USER_ID>0)');if(!$order)throw new RuntimeException('Natural order absent');$order=$order[0];$owner=['id'=>(int)$order['user_id'],'uuid'=>'CDXMR3_ORDER_READER','type'=>0];$date=$order['meal_date'];$out['preflight_order_has_store_id']=array_key_exists('store_id',$order);
 $stage='source-orders';$orders=rr($db,'SELECT * FROM '.$p.'meal_order WHERE USER_ID=? AND IS_DELETE=0 AND MEAL_DATE=? ORDER BY BIND_TIME DESC',[$owner['id'],$date]);$stage='source-goods';$details=rr($db,'SELECT * FROM '.$p.'initial_menu WHERE MEAL_ORDER_ID=? ORDER BY ID',[(int)$order['id']]);
 $source=['areas'=>$areas,'shops'=>$shops,'stalls'=>$stalls,'orders'=>$orders,'selected_order_goods'=>$details];$before=dh($source);$out['source_before']=$before;$out['preflight']=['natural_admin'=>true,'areas'=>count($areas),'shops'=>count($shops),'stalls'=>count($stalls),'orders_in_scope'=>count($orders),'selected_goods'=>count($details)];
 $validShops=array_values(array_filter($shops,static function($v){return (int)$v['is_delete']===0;}));$shop=$validShops?end($validShops):null;$shopId=$shop?(int)$shop['id']:0;
 $cases=[
  'order_list'=>['GET','/api/order/list',['start_date'=>$date,'end_date'=>$date,'page'=>1],$owner],
  'order_detail'=>['GET','/api/order/detail',['orderId'=>(int)$order['id']],$owner],
  'order_detail_missing'=>['GET','/api/order/detail',['orderId'=>0],$owner],
  'restaurant_list'=>['POST','/api/dining_config/restaurantList',['page'=>1,'page_size'=>10],$admin],
  'restaurant_detail'=>['POST','/api/dining_config/restaurantDetail',['id'=>$shopId],$admin],
  'restaurant_detail_missing'=>['POST','/api/dining_config/restaurantDetail',[],$admin],
  'restaurant_options'=>['POST','/api/dining_config/restaurantOptions',[],$admin],
  'stall_options'=>['POST','/api/dining_config/stallOptions',[],$admin],
 ];
 $cases=array_intersect_key($cases,array_flip(['order_list','order_detail','order_detail_missing']));
 foreach($cases as $name=>$args){$stage=$name;[$r,$j,$wire]=rq(...$args);if(is_array($j)&&($j['code']??-1)===0){$d=$j['data'];
   if($name==='order_list'){$r['rows']=count($d['data']??[]);$r['source_match']=($d['total']??-1)===count($orders);$r['ordered_desc']=true;$last=null;foreach($d['data'] as $v){$vsrc=rr($db,'SELECT ID,ORDER_NO,BIND_TIME,MEAL_TIMES,TOTAL_PRICE,MEAL_NUMBER,PAY_STATUS,SOURCE FROM '.$p.'meal_order WHERE ID=? AND USER_ID=?',[(int)$v['id'],$owner['id']]);if(count($vsrc)!==1){$r['source_match']=false;continue;}$vsrc=$vsrc[0];$r['source_match']=$r['source_match']&&$v['order_no']===$vsrc['order_no']&&$v['bind_time']===$vsrc['bind_time']&&$v['total_price']===number_format((float)$vsrc['total_price'],2,'.','');if($last!==null&&$v['bind_time']>$last)$r['ordered_desc']=false;$last=$v['bind_time'];}foreach($wire->data->data as $row)if((int)$row->id===(int)$order['id'])$r['selected_order_wire_shape']=wireShape($row);}
   elseif($name==='order_detail'){$r['source_match']=(int)($d['id']??0)===(int)$order['id']&&($d['order_no']??null)===$order['order_no'];$r['verified_root_decimals']=0;foreach($out['schema']['meal_order'] as $col){$key=strtolower($col['column_name']);if(strtolower($col['data_type'])==='decimal'&&(int)$col['precision_value']===10&&(int)$col['scale_value']===2&&array_key_exists($key,$d)){$r['source_match']=$r['source_match']&&$d[$key]===number_format((float)$order[$key],2,'.','');$r['verified_root_decimals']++;}}$r['goods_rows']=count($d['goods']??[]);$r['goods_source_match']=$r['goods_rows']===count($details);foreach($d['goods'] as $item){$raw=rr($db,'SELECT * FROM '.$p.'initial_menu WHERE ID=? AND MEAL_ORDER_ID=?',[(int)$item['id'],(int)$order['id']]);if(count($raw)!==1){$r['goods_source_match']=false;continue;}foreach(['dishes_weight','dishes_price','total_price'] as $key)$r['goods_source_match']=$r['goods_source_match']&&$item[$key]===number_format((float)$raw[0][$key],2,'.','');}}
   elseif($name==='restaurant_list'){$r['rows']=count($d['data']??[]);$r['source_match']=($d['total']??-1)===count($validShops);$r['ordered_desc']=true;$last=null;foreach($d['data'] as $v){$vsrc=rr($db,'SELECT * FROM '.$p.'restaurant_shop WHERE ID=?',[(int)$v['id']]);$r['source_match']=$r['source_match']&&count($vsrc)===1&&$v['name']===$vsrc[0]['name'];$count=0;foreach($stalls as $s)if((int)$s['restaurant_shop_id']===(int)$v['id']&&(int)$s['is_delete']===2&&(int)$s['status']===1)$count++;$r['source_match']=$r['source_match']&&$v['stall_count']===$count;if($last!==null&&(int)$v['id']>$last)$r['ordered_desc']=false;$last=(int)$v['id'];}}
   elseif($name==='restaurant_detail'){$r['source_match']=$shop!==null&&(int)($d['id']??0)===$shopId&&$d['name']===$shop['name'];}
   elseif(in_array($name,['restaurant_options','stall_options'],true)){$r['rows']=count($d);$expected=[];foreach($areas as $area){if((int)$area['is_delete']!==0||(int)$area['status']!==1)continue;$children=[];foreach($shops as $s){if((int)$s['is_delete']!==0||(int)$s['status']!==1||(int)$s['area_id']!==(int)$area['id'])continue;if($name==='restaurant_options')$children[]=['value'=>(int)$s['id'],'label'=>$s['name']];else{$leaf=[];foreach($stalls as $stall)if((int)$stall['is_delete']===2&&(int)$stall['status']===1&&(int)$stall['restaurant_shop_id']===(int)$s['id'])$leaf[]=['value'=>(int)$stall['id'],'label'=>$stall['name']];$children[]=['value'=>'B'.$s['id'],'label'=>$s['name'],'children'=>$leaf];}}$expected[]=['value'=>'A'.$area['id'],'label'=>$area['name'],'children'=>$children];}$r['source_match']=$d===$expected;$r['source_structure_sha']=dh(shape($expected));}
  }
  if($name==='order_detail'&&is_array($j)&&($j['code']??-1)===0){
   $visible=array_diff_key($order,array_flip(['is_delete','create_time','update_time']));$actual=$j['data'];unset($actual['goods']);
   $r['root_field_set_match']=array_keys(array_change_key_case(array_fill_keys(array_keys($actual),1)))===array_keys($visible);
   $ak=array_keys($actual);$vk=array_keys($visible);sort($ak);sort($vk);$r['root_field_set_match']=$ak===$vk;
   $types=[];foreach($out['schema']['meal_order'] as $col)$types[strtolower($col['column_name'])]=$col;
   $mismatch=[];foreach($visible as $key=>$value){$col=$types[$key];$type=strtolower($col['data_type']);$expected=$value;
    if($value!==null&&preg_match('/^(tinyint|smallint|mediumint|int|integer|bigint|bit)$/',$type))$expected=(int)$value;
    elseif($value!==null&&$type==='decimal'&&(int)$col['precision_value']===10&&(int)$col['scale_value']===2)$expected=number_format((float)$value,2,'.','');
    elseif($value!==null&&in_array($type,['float','double','real'],true))$expected=(float)$value;
    $got=$actual[$key]??null;
    $exactWideInteger=$key==='account_settlement_id'&&$type==='decimal'&&(int)$col['precision_value']===20&&(int)$col['scale_value']===0&&is_int($got)&&(string)$expected===(string)$got;
    if($expected!==$got&&!$exactWideInteger&&!(is_numeric($expected)&&is_numeric($got)&&is_float($expected)&&is_float($got)&&abs($expected-$got)<0.00000001))$mismatch[]=$key;
   }
   $r['all_root_source_fields_checked']=count($visible);$r['root_source_mismatch_fields']=$mismatch;$r['bank_user_name_present']=array_key_exists('bank_user_name',$actual);$r['bank_user_name_source_equal']=array_key_exists('bank_user_name',$actual)&&$actual['bank_user_name']===$order['bank_user_name'];
  }
  $out['routes'][$name]=$r;
 }
 $afterParts=[];foreach(['areas'=>'restaurant_area','shops'=>'restaurant_shop','stalls'=>'restaurants'] as $key=>$table){if(isset($out['source_limits'][$key])){$afterParts[$key]=[];continue;}$afterParts[$key]=rr($db,'SELECT * FROM '.$p.$table.' ORDER BY ID ASC');}
 $sourceAfter=['areas'=>$afterParts['areas'],'shops'=>$afterParts['shops'],'stalls'=>$afterParts['stalls'],'orders'=>rr($db,'SELECT * FROM '.$p.'meal_order WHERE USER_ID=? AND IS_DELETE=0 AND MEAL_DATE=? ORDER BY BIND_TIME DESC',[$owner['id'],$date]),'selected_order_goods'=>rr($db,'SELECT * FROM '.$p.'initial_menu WHERE MEAL_ORDER_ID=? ORDER BY ID',[(int)$order['id']])];$out['source_unchanged']=dh($sourceAfter)===$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);
