<?php
declare(strict_types=1);
/** Public synthetic FLOAT(8,2) physical-read regression; stdout only, no DB/HTTP/files. */
namespace think {
    class Exception extends \Exception {}
    class Db {
        public static $rows=[],$calls=[];
        public static function name($table){self::$calls[]=['name',$table];return new \MDMemoryQuery(self::$rows[$table]??[]);}
        public static function startTrans(){self::$calls[]=['begin'];}public static function commit(){self::$calls[]=['commit'];}public static function rollback(){self::$calls[]=['rollback'];}
    }
}
namespace app\model {
    class BaseModel {
        public $rows=[],$connection,$dbCalls=[];
        public function db($new=true){$this->dbCalls[]=$new;return $this;}public function getConnection(){return $this->connection;}
        public function __call($method,$args){if(!in_array($method,['alias','join','where','field','group','order','lock'],true))throw new \RuntimeException('Unfrozen ORM '.$method);return $this;}
        public function select(){return new \MDMemoryCollection($this->rows);}
    }
}
namespace {
$application=realpath($argv[1]??dirname(__DIR__,2).'/application');
if(!$application||!is_dir($application)){echo json_encode(['error'=>'application root missing','usage'=>'php nutrition_md_physical_reads_test.php /absolute/store/application']),PHP_EOL;exit(78);}
$repository=dirname($application);
foreach(['thinkphp/library/think/db/Expression.php','thinkphp/library/think/db/Connection.php','thinkphp/library/think/db/connector/Mysql.php']as $f)require_once $repository.'/'.$f;
require_once $application.'/cores/database/Dm.php';require_once $application.'/common/service/NutritionFloat82Write.php';
if(is_file($application.'/common/service/NutritionPlainFloatWeight.php'))require_once $application.'/common/service/NutritionPlainFloatWeight.php';
foreach(['p/logic/Nutrition.php','m/logic/Nutrition.php','model/NutritionTemplateExt.php','model/NutritionSchemeExt.php','model/NutritionScheme.php','p/logic/NutritionScheme.php','p/logic/NutritionStation.php','p/logic/NutritionTemplate.php']as $f)require_once $application.'/'.$f;
function getImgFullPath($v){return $v;}
function config($key){if($key==='database.type')return $GLOBALS['md_config']??'mysql';return ['use_number_unit'=>[['id'=>1,'item'=>'公开单位']],'train_status'=>[['id'=>1,'name'=>'公开状态']],'train_time'=>[['id'=>1,'name'=>'公开时段']]][$key]??[];}
class MDMemoryCollection {private $rows;public function __construct($r){$this->rows=$r;}public function toArray(){return $this->rows;}}
class MDMemoryQuery {
    private $rows;public function __construct($r){$this->rows=$r;}public function __call($m,$a){if(!in_array($m,['alias','join','where','field','order'],true))throw new \RuntimeException('Unfrozen query '.$m);return $this;}
    public function select(){return $this->rows;}public function column($f,$k=null){$r=[];foreach($this->rows as $v){if($k!==null)$r[$v[$k]]=$v[$f];else $r[]=$v[$f];}return $r;}
}
class MDNutrition extends \app\model\BaseModel {
    public $row=[];public function read($uuid){return $this->row;}public function inquiryNutrition($where,$p,$size,$order,$field){return [$this->row];}public function inquiryCount($where){return 1;}public function nutritionSet($where,$field,$group=false){return [$this->row];}
}
class MDDictionary {public function inquiryColumn($where,$field){return ['公开分类'];}}
class MDP extends \app\p\logic\Nutrition {public function __construct($m){$this->model=$m;$this->modelNutritionCategory=new MDDictionary();}}
class MDM extends \app\m\logic\Nutrition {public function __construct($m){$this->model=$m;}}
class MDTemplate extends \app\p\logic\NutritionTemplate {public function __construct($m,$ext){$this->model=$m;$this->modelExt=$ext;}}
class MDStaff {public function read($uuid){return ['sport_event_uuid'=>'PUBLIC-SPORT','head_img'=>''];}public function getStaffByDepartment($w,$f){return [['name'=>'公开部门']];}}
class MDSport {public function inquiryColumn($w,$f){return ['公开运动'];}}
class MDConsumption extends MDMemoryQuery {public function select(){return new MDMemoryCollection(parent::select());}}
class MDScheme extends \app\p\logic\NutritionScheme {public function __construct($m,$row){$this->nutritionModel=$m;$this->staffModel=new MDStaff();$this->sportModel=new MDSport();$this->nutritionConsumeModel=new MDConsumption([$row]);}}
class MDSlot {public function where($w){return $this;}public function lock($v){return $this;}public function find(){return [];}public function insertGetId($r){return 1;}public function inquiryColumn($w,$f){return [1];}public function add($r,$replace=false){return 1;}public function delete(){return 1;}}
class MDStation extends \app\p\logic\NutritionStation {public function __construct($m){$this->modelNutrition=$m;$this->modelExt=new MDSlot();$this->nutritionCuboardModel=new MDSlot();$this->model=new MDSlot();}}
$checks=0;$failures=[];$check=static function($name,$ok)use(&$checks,&$failures){$checks++;if(!$ok)$failures[]=$name;};
$run=static function($name,$call)use($check){try{return $call();}catch(\Throwable $e){$check($name.' exception '.get_class($e).': '.$e->getMessage(),false);return null;}};
$raw=['power'=>12.350000381469727,'carbohydrate'=>0.009999999776482582,'protein'=>12.340000152587891,'fat'=>12.350000381469727,'sodium'=>1.0099999904632568];
$getter=['power'=>12.35,'carbohydrate'=>0.01,'protein'=>12.34,'fat'=>12.35,'sodium'=>1.01];
$expectRows=static function($name,$actual,$expected)use($check){foreach($expected as $f=>$v)$check($name.' '.$f,array_key_exists($f,$actual??[])&&$actual[$f]===$v);$check($name.' plain use_weight untouched',array_key_exists('use_weight',$actual??[])&&$actual['use_weight']===1.25);};
$dm=(new \ReflectionClass(\app\cores\database\Dm::class))->newInstanceWithoutConstructor();$mysql=(new \ReflectionClass(\think\db\connector\Mysql::class))->newInstanceWithoutConstructor();
foreach([[$dm,true],[$mysql,false]]as [$connection,$isDm]){
    $GLOBALS['md_config']=$isDm?'mysql':'app\\cores\\database\\Dm';
    // Source oracle: fixed decimals=2 mysqlnd getter; synthetic row, no private snapshot.
    $numbers=$isDm?$raw:$getter;
    $row=['id'=>2,'uuid'=>'PUBLIC-NUTRITION','name'=>'公开MD回归商品','set_uuid'=>'PUBLIC-SET','nutrition_category_uuid'=>'PUBLIC-CATEGORY','use_weight'=>1.25,'use_number'=>1,'use_weight_unit'=>1,'use_number_unit'=>1,'use_time'=>'PUBLIC-TIME','use_method'=>'公开方法','employ'=>1,'supplier'=>'公开供应商','create_time'=>'2020-01-02 03:04:05','specifications'=>'','img_path'=>'','source_img_path'=>'','app_img'=>'','train_status'=>1,'train_time'=>1,'consume_hour'=>'12:00','use_volume'=>1000000000]+$numbers;
    $m=new MDNutrition();$m->row=$row;$m->connection=$connection;$p=new MDP($m);$mobile=new MDM($m);
    $direct=\app\common\service\NutritionFloat82Write::physicalForModel($row,'nutrition',$m);
    $expectRows('direct existing helper model affinity',$direct,$getter);
    $other=\app\common\service\NutritionFloat82Write::physicalForModel($row,'nutrition_scheme_ext',$m);
    $check('unproven other-table alias untouched',$other===$row);
    foreach([['p',$p],['m',$mobile]]as [$name,$logic]){
        $res=$run($name.' info',static function()use($logic){return $logic->info('PUBLIC-NUTRITION');});
        $expectRows($name.' already covered info',$res['data']??null,$getter);
        $res=$run($name.' fixed lists',static function()use($logic){return $logic->lists([],1,1,[],['a.*','b.name as set_name']);});
        $expectRows($name.' fixed lists',$res['data']['rows'][0]??null,$getter);
        foreach([['a.*'],['SUM(a.fat) AS fat','SUM(a.power) AS power'],['a.*','b.name as set_name','a.fat+0e0 AS fat'],['CAST(a.fat AS DOUBLE) AS fat'],'a.fat+0e0 AS fat']as $projection){
            $res=$run($name.' non-frozen projection',static function()use($logic,$projection){return $logic->lists([],1,1,[],$projection);});
            $expectRows($name.' non-frozen projection',$res['data']['rows'][0]??null,$numbers);
        }
    }
    foreach(['NutritionSchemeExt','NutritionScheme']as $name){$class='\\app\\model\\'.$name;$model=new $class();$model->connection=$connection;$null=array_replace($row,array_fill_keys(array_keys($raw),null));$model->rows=[$row,$null];
        $res=$run($name,static function()use($model,$name){return $name==='NutritionScheme'?$model->schemeRead('PUBLIC-SCHEME',1):$model->inquiryAllInfo([]);});
        $expectRows($name.' fixed join',$res[0]??null,$getter);
        $expectRows($name.' left join null',$res[1]??null,array_fill_keys(array_keys($raw),null));
    }
    // TemplateExt deliberately retains its raw model return: the sole audited consumer already maps it.
    $ext=new \app\model\NutritionTemplateExt();$ext->connection=$connection;$ext->rows=[$row];
    $rawExt=$ext->inquiryAllInfo([]);$expectRows('TemplateExt raw model contract',$rawExt[0]??null,$numbers);
    $template=new MDTemplate($m,$ext);$res=$run('TemplateExt existing consumer',static function()use($template){return $template->info('PUBLIC-TEMPLATE');});
    $expectRows('TemplateExt already covered consumer',$res['data']['template_ext'][0]??null,$getter);
    \think\Db::$rows=['nutrition'=>[$row],'nutrition_time'=>[['uuid'=>'PUBLIC-TIME','name'=>'公开时段']]];
    $res=$run('UseTimeGroup',static function()use($mobile){return $mobile->NutritionUseTimeGroup('PUBLIC-NUTRITION');});
    $expectRows('mobile UseTimeGroup',$res['data'][0]??null,$getter);
    $res=$run('mobile getNutritionTime',static function()use($mobile){return $mobile->getNutritionTime(['PUBLIC-NUTRITION']);});
    $expectRows('mobile literal nutritionSet',$res['data']['nutrition_array'][0]??null,$getter);
    $scheme=new MDScheme($m,$row);
    $res=$run('PC getNutritionTime',static function()use($scheme){return $scheme->getNutritionTime('PUBLIC-NUTRITION');});
    $expectRows('PC literal nutritionSet',$res['data']['nutrition_array'][0]??null,$getter);
    $res=$run('PC ConsumeActual',static function()use($scheme){return $scheme->getNutritionConsumeActual('PUBLIC-STAFF','2020-01-02');});
    $expectRows('PC physical consume before arithmetic',$res['nutrition_consume'][0]??null,$getter);
    $check('PC original arithmetic receives MD getter values',($res['nutrition_content']['fat']??null)===round(1.25*$row['use_volume']*$getter['fat']/100));
    $sum=$scheme->nutritionSum([$row]);
    $check('generic nutritionSum caller MD values not reformatted',$sum['fat']===round(1.25*$row['use_volume']*$numbers['fat']/100));
    $station=new MDStation($m);$res=$run('Station Nutrition.read return',static function()use($station){return $station->updateExt(['station_uuid'=>'PUBLIC-STATION','num'=>1,'nutrition_uuid'=>'PUBLIC-NUTRITION']);});
    $expectRows('Station physical read return',$res['data']??null,$getter);
}
foreach(['12.35',12,12.35,null,['public'=>'raw']]as $value){$row=array_fill_keys(array_keys($raw),$value)+['use_weight'=>1.25];$check('MySQL original MD field types unchanged',\app\common\service\NutritionFloat82Write::physicalRow($row,'nutrition',false)===$row);}
$source=static function($class,$method){$r=new \ReflectionMethod($class,$method);return implode('',array_slice(file($r->getFileName()),$r->getStartLine()-1,$r->getEndLine()-$r->getStartLine()+1));};
foreach([[\app\p\logic\NutritionScheme::class,'getRecomment',2],[\app\p\logic\NutritionScheme::class,'delNutrition',1],[\app\m\logic\Nutrition::class,'getTarget',1]]as [$class,$method,$count])$check($method.' source-only MD fixed-caller hooks',substr_count($source($class,$method),'NutritionFloat82Write::physicalForModel')===$count);
$check('generic nutritionSum has no MD physical formatter',strpos($source(\app\p\logic\NutritionScheme::class,'nutritionSum'),'NutritionFloat82Write::physicalForModel')===false);
echo json_encode(['test'=>'five MD explicit physical reads','application'=>$application,'assertions'=>$checks,'failures'=>$failures,'passed'=>!$failures,'HTTP'=>0,'PDO_connections'=>0,'SQL'=>0,'file_writes'=>0,'memory_operations'=>count(\think\Db::$calls),'oracle'=>'Public positive promoted32 constants -> fixed-D2 getter; first fat pair also independently proven by actual Lists DTO','limits'=>'Fake ORM / actual unconnected connector classes; three large caller flows source-only; no new native acceptance or write/rounding/capacity claims'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),PHP_EOL;
exit($failures?1:0);
}

