<?php
declare(strict_types=1);
date_default_timezone_set('Asia/Shanghai');ini_set('display_errors','0');ini_set('log_errors','0');require __DIR__.'/common.php';
$lane=getenv('PILOT_LANE');$root=getenv('COUPON_APP_ROOT');$runtime=getenv('PILOT_RUNTIME');$base=getenv('POC_BASE_URL');$tenant=1930501010;$marker='CDXAC10E4C6A28B1D09';$dm=$lane==='C';
if(!in_array($base,['http://127.0.0.1:18361','http://127.0.0.1:18362'],true)){exit(78);}
$db=cpPdo();$snapshot=static function()use($db,$tenant):array{return ['coupon'=>cpDigest(cpRows($db,'SELECT * FROM yoshop_coupon WHERE store_id<>?',[$tenant])),'user_coupon'=>cpDigest(cpRows($db,'SELECT * FROM yoshop_user_coupon'))];};
$before=$snapshot();$app=cpBootstrap($root,$runtime);$token=bin2hex(random_bytes(24));$ids=[];$cases=[];$responses=[];$sources=[];$error=null;
$same=static function($a,$b)use(&$same):bool{if(gettype($a)!==gettype($b)){return false;}if(is_object($a)){$a=get_object_vars($a);$b=get_object_vars($b);ksort($a);ksort($b);}if(is_array($a)){if(array_keys($a)!==array_keys($b)){return false;}foreach($a as $k=>$v){if(!$same($v,$b[$k])){return false;}}return true;}return $a===$b;};
$read=static function(string $module)use($db,$tenant,$marker,$dm):array{$cast=$dm?'VARCHAR(64)':'CHAR';return cpRows($db,'SELECT c.*,CAST(reduce_price AS '.$cast.') AS exact_reduce_price,CAST(min_price AS '.$cast.') AS exact_min_price,CAST(write_off_limit_amount AS '.$cast.') AS exact_write_off_limit_amount FROM yoshop_coupon c WHERE store_id=? AND name LIKE ? ORDER BY coupon_id',[$tenant,$marker.'_'.$module.'%']);};
$expected=static function(array $row):object{$out=$row;foreach(['reduce_price','min_price','write_off_limit_amount'] as $f){$exact=$row['exact_'.$f];if(!is_string($exact)){throw new RuntimeException('Exact source decimal string gate');}$out[$f]=bcadd($exact,'0',2);unset($out['exact_'.$f]);}$out['discount']=$row['discount']/10;$out['start_time']=$row['start_time']?date('Y/m/d',(int)$row['start_time']):null;$out['end_time']=$row['end_time']?date('Y/m/d',(int)$row['end_time']):null;$out['apply_range_config']=$row['apply_range_config']?json_decode($row['apply_range_config'],true):[];foreach(['create_time','update_time'] as $f){$out[$f]=$row[$f]?date('Y-m-d H:i:s',(int)$row[$f]):null;}$out['state']=['text'=>'正常','value'=>1];return json_decode(json_encode($out,JSON_UNESCAPED_UNICODE|JSON_THROW_ON_ERROR));};
$request=static function(string $name,string $method,string $module,string $action,array $args=[])use($base,$tenant,$token,&$responses):array{$path='/'.$module.'/market.Coupon/'.$action;$opts=['method'=>$method,'header'=>"Host: cdx-admin-coupon.invalid\r\nPlatform: POC\r\nAccess-Token: ".$token."\r\nX-Pilot-Scenario: PILOT_ACP_".$name."\r\n",'ignore_errors'=>true,'follow_location'=>0,'timeout'=>25];$url=$base.$path;if($method==='GET'){$url.='?'.http_build_query(['storeId'=>$tenant]+$args);}else{$opts['header'].="Content-Type: application/json\r\n";$opts['content']=json_encode(['storeId'=>$tenant]+$args,JSON_UNESCAPED_UNICODE);}$start=time();$raw=file_get_contents($url,false,stream_context_create(['http'=>$opts]));$end=time();preg_match('~^HTTP/\S+ (\d+)~',$http_response_header[0]??'',$m);$r=['method'=>$method,'path'=>$path,'http_status'=>(int)($m[1]??0),'body'=>json_decode((string)$raw),'request_time_window'=>[$start,$end]];$responses[$name]=$r;return $r;};
$record=static function(string $name,array $r,bool $pass)use(&$cases):void{$cases[]=['case'=>$name,'method'=>$r['method'],'path'=>$r['path'],'http_status'=>$r['http_status'],'status'=>$r['body']->status??null,'source_pass'=>$pass];};
$list=static function(string $name,string $module,bool $visible)use($request,$read,$expected,$same,$record,&$sources):void{$r=$request($name,'GET',$module,'list');$raw=$read($module);$list=$r['body']->data->list->data??null;$ok=$r['http_status']===200&&($r['body']->status??null)===200&&is_array($list)&&count($list)===($visible?1:0);if($visible){$ok=$ok&&count($raw)===1&&$same($expected($raw[0]),$list[0]);}$sources[$name]=['rows'=>$raw,'expected'=>$visible&&$raw?$expected($raw[0]):null];$record($name,$r,$ok);};
$detail=static function(string $name,string $module,int $id)use($request,$read,$expected,$same,$record,&$sources):void{$r=$request($name,'GET',$module,'detail',['couponId'=>$id]);$raw=$read($module);$ok=$r['http_status']===200&&($r['body']->status??null)===200&&count($raw)===1&&$same($expected($raw[0]),$r['body']->data->detail??null);$sources[$name]=['rows'=>$raw,'expected'=>$raw?$expected($raw[0]):null];$record($name,$r,$ok);};
try{
    $boundary=json_decode(file_get_contents($base.'/__poc_boundary'),true);if(empty($boundary['new_private_root'])||empty($boundary['external_stubs'])||empty($boundary['file_cache'])){throw new RuntimeException('Boundary missing');}
    \think\facade\Cache::set($token,['user'=>['store_id'=>$tenant,'store_user_id'=>1930501011,'real_name'=>$marker,'user_name'=>$marker,'is_super'=>1],'store_id'=>$tenant,'is_login'=>true],3600);file_put_contents($runtime.'/fixture_map.json',json_encode(['marker'=>$marker,'tenant'=>$tenant,'token'=>$token]));chmod($runtime.'/fixture_map.json',0600);
    foreach(['store'=>'S','terminal'=>'T'] as $module=>$tag){
        if($read($module)){throw new RuntimeException('Self marker collision');}$list($tag.'_EMPTY',$module,false);
        $form=['name'=>$marker.'_'.$module.'中文 "券"','coupon_type'=>10,'reduce_price'=>'12.34','discount'=>0,'min_price'=>'56.78','expire_type'=>10,'expire_day'=>7,'apply_range'=>10,'apply_range_config'=>[],'total_num'=>10,'describe'=>"自有 '说明'",'status'=>1,'sort'=>10,'write_off_limit_amount'=>'1234.56','unavailable_dates'=>'["2026-10-10"]','unavailable_time_ranges'=>null];
        $r=$request($tag.'_ADD','POST',$module,'add',['form'=>$form]);$raw=$read($module);$ok=$r['http_status']===200&&($r['body']->status??null)===200&&count($raw)===1;
        if($raw){$ids[$module]=(int)$raw[0]['coupon_id'];$ok=$ok&&$raw[0]['name']===htmlspecialchars(trim($form['name']),ENT_COMPAT)&&$raw[0]['start_time']===0&&$raw[0]['end_time']===0;foreach(['create_time','update_time'] as $f){$ok=$ok&&(int)$raw[0][$f]>=$r['request_time_window'][0]&&(int)$raw[0][$f]<=$r['request_time_window'][1];}}
        $record($tag.'_ADD',$r,$ok);if(!$ok){throw new RuntimeException('Own add failed');}$sources[$tag.'_ADD']=['rows'=>$raw,'form'=>$form];$list($tag.'_LIST',$module,true);$detail($tag.'_DETAIL',$module,$ids[$module]);
        $form['name']=$marker.'_'.$module.'编辑 "菜券"';$form['reduce_price']='23.45';$form['min_price']='78.90';$form['write_off_limit_amount']='2345.67';$form['expire_type']=20;$form['betweenTime']=['2026-10-01','2030-12-31'];$form['apply_range']=20;$form['apply_range_config']=['applyGoodsIds'=>[1930501012]];$form['sort']=20;$form['unavailable_dates']=null;$form['unavailable_time_ranges']='["12:00-13:00"]';
        $r=$request($tag.'_EDIT','POST',$module,'edit',['couponId'=>$ids[$module],'form'=>$form]);$raw=$read($module);$ok=$r['http_status']===200&&($r['body']->status??null)===200&&count($raw)===1&&$raw[0]['name']===htmlspecialchars(trim($form['name']),ENT_COMPAT)&&(int)$raw[0]['start_time']===strtotime('2026-10-01 00:00:00')&&(int)$raw[0]['end_time']===strtotime('2030-12-31 23:59:59');$record($tag.'_EDIT',$r,$ok);$sources[$tag.'_EDIT']=['rows'=>$raw,'form'=>$form];$list($tag.'_LIST_EDITED',$module,true);$detail($tag.'_DETAIL_EDITED',$module,$ids[$module]);
        $r=$request($tag.'_DELETE','POST',$module,'delete',['couponId'=>$ids[$module]]);$raw=$read($module);$record($tag.'_DELETE',$r,$r['http_status']===200&&($r['body']->status??null)===200&&count($raw)===1&&$raw[0]['is_delete']===1);$list($tag.'_SOFT_DELETED',$module,false);
    }
}catch(Throwable $e){$error=['class'=>get_class($e),'file'=>basename($e->getFile()),'line'=>$e->getLine(),'message_sha256'=>hash('sha256',$e->getMessage())];}
\think\facade\Cache::delete($token);$tokenCleaned=!\think\facade\Cache::has($token);\think\facade\Cache::clear();$after=$snapshot();$owned=[];foreach(['store','terminal'] as $module){$owned[$module]=$read($module);}
$retained=[];foreach($owned as $module=>$rows){foreach($rows as $row){$retained[]=['module'=>$module,'coupon_id'=>$row['coupon_id'],'name'=>$row['name'],'store_id'=>$row['store_id'],'is_delete'=>$row['is_delete']];}}
$external=is_file($runtime.'/external-private.jsonl')?count(file($runtime.'/external-private.jsonl')):0;$events=[];if(is_file($runtime.'/queries.jsonl')){foreach(file($runtime.'/queries.jsonl',FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) as $line){$events[]=json_decode($line,true);}}
$sha=[];foreach(['app/store/controller/market/Coupon.php','app/terminal/controller/market/Coupon.php','app/store/model/Coupon.php','app/terminal/model/Coupon.php','app/common/model/Coupon.php'] as $f){$sha[$f]=hash_file('sha256',$root.'/'.$f);}
$report=['lane'=>$lane,'cases'=>$cases,'unexpected'=>$error,'before'=>$before,'after'=>$after,'natural_unchanged'=>$before===$after,'retained_owned'=>$retained,'token_cleaned'=>$tokenCleaned,'external_attempts'=>$external,'source_sha256'=>$sha,'events'=>$events,'fixture_policy'=>'retain exact owned soft-deleted business rows; no physical DELETE'];
foreach(['report.json'=>$report,'responses.json'=>$responses,'source-private.json'=>$sources,'fixture_map.json'=>['marker'=>$marker,'tenant'=>$tenant,'coupon_ids'=>$ids]] as $f=>$v){file_put_contents($runtime.'/'.$f,json_encode($v,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR));chmod($runtime.'/'.$f,0600);}
echo json_encode(['lane'=>$lane,'cases'=>count($cases),'source_failures'=>count(array_filter($cases,static function($c){return !$c['source_pass'];})),'unexpected'=>$error!==null,'natural_unchanged'=>$before===$after,'retained_owned'=>count($retained),'external_attempts'=>$external]),PHP_EOL;
exit($error!==null||$before!==$after||!$tokenCleaned||$external?1:0);
