<?php

declare(strict_types=1);

// REVIEW DRAFT. Existing business rows are SELECT-only. Writes are limited to a
// synthetic type-1 operator, role and four role-menu links, then exact cleanup.
if ((string)getenv('CDXPARCF_READONLY_EXECUTE') !== 'ROOT_APPROVED_EXACT_SHA') {
    fwrite(STDERR,"Review draft; execution disabled\n");
    exit(2);
}
set_exception_handler(static function (Throwable $error): void {
    fwrite(STDERR,'CDXPARCF_READONLY_PRECHECK_FAILED '.get_class($error)
        .' code='.(string)$error->getCode().PHP_EOL);
    exit(2);
});
$lane=(string)getenv('CDXPARCF_LANE');
$root=(string)getenv('POC_APP_ROOT');
$base=rtrim((string)getenv('POC_BASE_URL'),'/');
$runtimes=[
    'A'=>['/tmp/zhct-http-store-a.l5omH8','http://127.0.0.1:18097'],
    'B'=>['/tmp/zhct-http-store-mysql.dQTTBP','http://127.0.0.1:18081'],
    'C'=>['/tmp/zhct-dm-poc-20260928.fcpiiN/store','http://127.0.0.1:18080'],
];
if (!isset($runtimes[$lane]) || $root!==$runtimes[$lane][0]
    || $base!==$runtimes[$lane][1]
    || !is_file($root.'/application/base/Security.php')) {
    throw new RuntimeException('Approved isolated Store runtime required');
}
$boundary=json_decode((string)file_get_contents($base.'/__poc_boundary'),true);
if (!is_array($boundary) || empty($boundary['external_stubs'])
    || empty($boundary['redis_isolated']) || !empty($boundary['real_queue'])) {
    throw new RuntimeException('Store isolation boundary missing');
}

if ($lane==='C') {
    if ((string)getenv('POC_DIALECT')!=='dm') {
        throw new RuntimeException('C DM dialect required');
    }
    $pid=(string)getenv('POC_RUNTIME_PID');
    $cmdline=ctype_digit($pid)
        ? (string)file_get_contents('/proc/'.$pid.'/cmdline') : '';
    if (!ctype_digit($pid) || strpos($cmdline,'127.0.0.1:18080')===false
        || strpos($cmdline,$root.'/public')===false) {
        throw new RuntimeException('C PID/port/docroot mismatch');
    }
    $context=array_fill_keys(['POC_HOST','POC_PORT','POC_SCHEMA',
        'POC_USER','POC_PASSWORD'],'');
    foreach ($context as $key=>$_) $context[$key]=(string)getenv($key);
    if ($context['POC_HOST']==='') {
        foreach (explode("\0",(string)file_get_contents('/proc/'.$pid.'/environ')) as $entry) {
            $pair=explode('=',$entry,2);
            if (count($pair)===2 && array_key_exists($pair[0],$context)) {
                $context[$pair[0]]=$pair[1];
            }
        }
    }
    if ($context['POC_HOST']!=='127.0.0.1'
        || $context['POC_PORT']!=='5237'
        || strtoupper($context['POC_SCHEMA'])!=='ZHCT_APP'
        || strtoupper($context['POC_USER'])!=='ZHCT_APP'
        || $context['POC_PASSWORD']==='') {
        throw new RuntimeException('Unexpected C target');
    }
    $open=static function () use ($context): PDO {
        return new PDO('dm:host=127.0.0.1;port=5237;schema=ZHCT_APP',
            $context['POC_USER'],$context['POC_PASSWORD'],[
                PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_EMULATE_PREPARES=>false,
                PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC,
            ]);
    };
    $table=static function (string $name): string { return strtoupper($name); };
    $limit=static function (int $n): string { return 'FETCH FIRST '.$n.' ROWS ONLY'; };
} else {
    if ((string)getenv('POC_DIALECT')!=='mysql') {
        throw new RuntimeException('Code local MySQL dialect required');
    }
    $localPath=realpath(__DIR__
        . '/../../../../../zhctproject/store/application/config/local/database.php');
    if (!$localPath || strpos($localPath,
        '/zhctproject/store/application/config/local/')===false) {
        throw new RuntimeException('Approved local DB config absent');
    }
    $load=static function (string $path): array {
        $config=require $path;
        if (!is_array($config)) throw new RuntimeException('Invalid DB config');
        return $config;
    };
    $local=$load($localPath);
    $selected=$load($root.'/application/database.php');
    foreach (['hostname','database','username','password'] as $field) {
        if (!isset($local[$field],$selected[$field])
            || $local[$field]!==$selected[$field]) {
            throw new RuntimeException('A/B runtime not code local development DB');
        }
    }
    if ((int)($local['hostport']??3306)
        !==(int)($selected['hostport']??3306)) {
        throw new RuntimeException('A/B runtime DB port mismatch');
    }
    $dsn='mysql:host='.$local['hostname'].';port='.(int)($local['hostport']??3306)
        .';dbname='.$local['database'].';charset=utf8mb4';
    $open=static function () use ($dsn,$local): PDO {
        return new PDO($dsn,(string)$local['username'],(string)$local['password'],[
            PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES=>false,
            PDO::ATTR_DEFAULT_FETCH_MODE=>PDO::FETCH_ASSOC,
            PDO::ATTR_TIMEOUT=>8,
        ]);
    };
    $table=static function (string $name): string { return strtolower($name); };
    $limit=static function (int $n): string { return 'LIMIT '.$n; };
}

function cdxCfRoUuid(): string
{
    $bytes=random_bytes(16);
    $bytes[6]=chr((ord($bytes[6])&0x0f)|0x40);
    $bytes[8]=chr((ord($bytes[8])&0x3f)|0x80);
    $hex=strtoupper(bin2hex($bytes));
    return substr($hex,0,8).'-'.substr($hex,8,4).'-'.substr($hex,12,4)
        .'-'.substr($hex,16,4).'-'.substr($hex,20,12);
}
function cdxCfRoRows(PDO $db,string $sql,array $args=[]): array
{
    $stmt=$db->prepare($sql);
    $stmt->execute($args);
    return array_map(static function(array $row): array {
        return array_change_key_case($row,CASE_LOWER);
    },$stmt->fetchAll(PDO::FETCH_ASSOC));
}
function cdxCfRoCount(PDO $db,string $sql,array $args=[]): int
{
    return (int)(cdxCfRoRows($db,$sql,$args)[0]['n']??-1);
}
function cdxCfRoSafeName(string $name): bool
{
    if ($name==='' || mb_strlen($name)>50 || preg_match('/[\x00-\x1f]/u',$name)) {
        return false;
    }
    foreach (["'",'"','\\','%','_'] as $unsafe) {
        if (strpos($name,$unsafe)!==false) return false;
    }
    return true;
}
function cdxCfRoPost(string $base,string $route,array $form,string $token=''): array
{
    $headers="Content-Type: application/x-www-form-urlencoded\r\n";
    if ($token!=='') $headers.='token: '.$token."\r\n";
    $context=stream_context_create(['http'=>[
        'method'=>'POST','header'=>$headers,
        'content'=>http_build_query($form,'','&',PHP_QUERY_RFC3986),
        'timeout'=>20,'ignore_errors'=>true,
    ]]);
    $body=@file_get_contents($base.$route,false,$context);
    $responseHeaders=$http_response_header??[];
    if (strpos((string)($responseHeaders[0]??''),' 200 ')===false
        || !is_string($body)) {
        throw new RuntimeException('Isolated HTTP transport failed');
    }
    $json=json_decode($body,true);
    if (!is_array($json)) throw new RuntimeException('Isolated HTTP not JSON');
    return $json;
}
function cdxCfRoFindById($tree,string $key,string $value): ?array
{
    if (!is_array($tree)) return null;
    if (isset($tree[$key]) && (string)$tree[$key]===$value) return $tree;
    foreach ($tree as $child) {
        $found=cdxCfRoFindById($child,$key,$value);
        if ($found!==null) return $found;
    }
    return null;
}
function cdxCfRoContains($tree,string $needle): bool
{
    if (is_string($tree)) return $tree===$needle;
    if (!is_array($tree)) return false;
    foreach ($tree as $child) {
        if (cdxCfRoContains($child,$needle)) return true;
    }
    return false;
}
function cdxCfRoNumericTotal($tree): float
{
    if (is_numeric($tree)) return (float)$tree;
    if (!is_array($tree)) return 0.0;
    $sum=0.0;
    foreach ($tree as $child) $sum+=cdxCfRoNumericTotal($child);
    return $sum;
}
function cdxCfRoHasUnique(PDO $db,string $lane,string $schema,
    string $table,array $expected): bool
{
    if ($lane==='C') {
        $rows=cdxCfRoRows($db,
            'SELECT i.INDEX_NAME,i.UNIQUENESS,ic.COLUMN_NAME,ic.COLUMN_POSITION'
            .' FROM USER_INDEXES i JOIN USER_IND_COLUMNS ic'
            .' ON i.INDEX_NAME=ic.INDEX_NAME WHERE i.TABLE_NAME=?'
            .' ORDER BY i.INDEX_NAME,ic.COLUMN_POSITION',[strtoupper($table)]);
    } else {
        $rows=cdxCfRoRows($db,
            'SELECT INDEX_NAME,NON_UNIQUE,SEQ_IN_INDEX,COLUMN_NAME'
            .' FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=?'
            .' AND TABLE_NAME=? ORDER BY INDEX_NAME,SEQ_IN_INDEX',
            [$schema,strtolower($table)]);
    }
    $indexes=[];
    foreach ($rows as $row) {
        $name=(string)$row['index_name'];
        $indexes[$name]['unique']=$lane==='C'
            ? strtoupper((string)$row['uniqueness'])==='UNIQUE'
            : (int)$row['non_unique']===0;
        $indexes[$name]['columns'][]=strtolower((string)$row['column_name']);
    }
    foreach ($indexes as $index) {
        if ($index['unique'] && $index['columns']===$expected) return true;
    }
    return false;
}

$db=$open();
if ($lane==='C') {
    if (strtoupper((string)$db->query(
        "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")
        ->fetchColumn())!=='ZHCT_APP') {
        throw new RuntimeException('Wrong C schema');
    }
} elseif (strpos((string)$db->query('SELECT VERSION()')->fetchColumn(),'5.6.')===false) {
    throw new RuntimeException('Wrong A/B MySQL version');
}
$t=[];
foreach (['dishes','dish','foods_composition','recipes','date_menu',
    'user','role','user_role','role_menu','menu'] as $name) {
    $t[$name]=$table('ydy_'.$name);
}
$menus=[
    'cater_select'=>['C5E8BD15-C890-11DF-183B-0A5444A528EF','p/cater/select'],
    'cater_find'=>['2CBDEFF2-EADF-628F-7CBD-05A16D3183C7','p/cater/find'],
    'food_lists'=>['39288C13-FDD4-7F47-35E6-3D9B35F9CB2C','p/food/lists'],
    'food_read'=>['352B7629-DC3D-4C69-9032-DE025F25090C','p/food/readbyid'],
];
$marker='CDXPARCFRO'.strtoupper(bin2hex(random_bytes(4)));
$userUuid=cdxCfRoUuid();
$roleUuid=cdxCfRoUuid();
$userId=0;
$roleId=0;
$dish=null;
$recipe=null;
$popular=null;
$phase='preflight';
$result=['lane'=>$lane,'marker'=>$marker,'cases'=>[],'cleanup'=>[]];

try {
    // The source business tables are never inserted, updated, or deleted.
    $required=[
        'user'=>['id','uuid','account','pwd','salt','name','type','status',
            'create_time','create_by','is_delete'],
        'role'=>['id','uuid','name','status','create_time','create_by'],
        'user_role'=>['id','user_uuid','role_uuid','create_time','create_by'],
        'role_menu'=>['id','role_uuid','menu_uuid','create_time','create_by'],
    ];
    $inserted=[
        'user'=>['uuid','account','pwd','salt','name','type','status',
            'create_time','create_by','is_delete'],
        'role'=>['uuid','name','status','create_time','create_by'],
        'user_role'=>['user_uuid','role_uuid','create_time','create_by'],
        'role_menu'=>['role_uuid','menu_uuid','create_time','create_by'],
    ];
    foreach (['user','role','user_role','role_menu'] as $key) {
        $name=$t[$key];
        if ($lane==='C') {
            $columns=cdxCfRoRows($db,
                'SELECT COLUMN_NAME,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS'
                .' WHERE TABLE_NAME=?',[strtoupper($name)]);
            $triggers=cdxCfRoCount($db,
                'SELECT COUNT(*) N FROM USER_TRIGGERS WHERE TABLE_NAME=?',
                [strtoupper($name)]);
            $fks=cdxCfRoCount($db,"SELECT COUNT(*) N FROM USER_CONSTRAINTS"
                ." WHERE TABLE_NAME=? AND CONSTRAINT_TYPE='R'",[strtoupper($name)]);
        } else {
            $columns=cdxCfRoRows($db,
                'SELECT COLUMN_NAME,IS_NULLABLE,COLUMN_DEFAULT'
                .' FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=?'
                .' AND TABLE_NAME=?',[$local['database'],$name]);
            $triggers=cdxCfRoCount($db,
                'SELECT COUNT(*) N FROM information_schema.TRIGGERS'
                .' WHERE TRIGGER_SCHEMA=? AND EVENT_OBJECT_TABLE=?',
                [$local['database'],$name]);
            $fks=cdxCfRoCount($db,
                'SELECT COUNT(*) N FROM information_schema.KEY_COLUMN_USAGE'
                .' WHERE TABLE_SCHEMA=? AND TABLE_NAME=?'
                .' AND REFERENCED_TABLE_NAME IS NOT NULL',
                [$local['database'],$name]);
        }
        if ($triggers!==0 || $fks!==0) {
            throw new RuntimeException('Permission fixture trigger/FK gate failed');
        }
        $present=array_map(static function(array $row): string {
            return strtolower((string)$row['column_name']);
        },$columns);
        if (array_diff($required[$key],$present)) {
            throw new RuntimeException('Permission fixture columns missing');
        }
        foreach ($columns as $column) {
            $columnName=strtolower((string)$column['column_name']);
            $notNull=$lane==='C'
                ? (string)$column['nullable']==='N'
                : (string)$column['is_nullable']==='NO';
            $default=$lane==='C' ? $column['data_default']
                : $column['column_default'];
            if ($columnName!=='id' && $notNull && $default===null
                && !in_array($columnName,$inserted[$key],true)) {
                throw new RuntimeException('Permission insert omits required field');
            }
        }
    }
    $indexSchema=$lane==='C'?'ZHCT_APP':(string)$local['database'];
    if (!cdxCfRoHasUnique($db,$lane,$indexSchema,$t['role'],['name'])
        || !cdxCfRoHasUnique($db,$lane,$indexSchema,
            $t['user_role'],['user_uuid','role_uuid'])
        || !cdxCfRoHasUnique($db,$lane,$indexSchema,
            $t['role_menu'],['role_uuid','menu_uuid'])) {
        throw new RuntimeException('Permission fixture unique-key gate failed');
    }
    foreach ($menus as $menu) {
        $rows=cdxCfRoRows($db,'SELECT FILTER FROM '.$t['menu']
            .' WHERE UUID=?',[$menu[0]]);
        $filters=count($rows)===1
            ? array_map(static function($part): string {
                return trim(strtolower($part),' /');
            },explode('|',(string)$rows[0]['filter'])) : [];
        if (count($rows)!==1 || !in_array($menu[1],$filters,true)) {
            throw new RuntimeException('Exact type1 read-menu/filter absent');
        }
    }
    if (cdxCfRoCount($db,'SELECT COUNT(*) N FROM '.$t['user']
            .' WHERE ACCOUNT LIKE ?', ['CDXPARCFRO%'])!==0
        || cdxCfRoCount($db,'SELECT COUNT(*) N FROM '.$t['role']
            .' WHERE NAME LIKE ?', ['CDXPARCFRO%'])!==0
        || cdxCfRoCount($db,'SELECT COUNT(*) N FROM '.$t['user_role']
            .' WHERE CREATE_BY LIKE ?', ['CDXPARCFRO%'])!==0
        || cdxCfRoCount($db,'SELECT COUNT(*) N FROM '.$t['role_menu']
            .' WHERE CREATE_BY LIKE ?', ['CDXPARCFRO%'])!==0) {
        throw new RuntimeException('Prior permission marker residue');
    }
    $result['cases'][]=['phase'=>'preflight',
        'four_read_menus_exact'=>true,'permission_prefix_zero'=>true];

    // Read only candidate d -> c -> f; prefer a natural recipe/date-menu chain.
    // Names stay in process memory and never enter result/evidence.
    $candidatePrefix='SELECT d.ID D_ID,d.UUID D_UUID,d.NAME D_NAME,'
        .' d.CATEGORY D_CATEGORY,d.WEIGHT D_WEIGHT,d.ENERGY D_ENERGY,'
        .' c.ID C_ID,c.MINUTE_SORT C_MINUTE_SORT,'
        .' f.ID F_ID,f.NAME F_NAME FROM '.$t['dishes'].' d'
        .' JOIN '.$t['dish'].' c ON c.DISHES_UUID=d.UUID'
        .' JOIN '.$t['foods_composition'].' f ON f.ID=c.COMPOSITION_ID';
    $candidateWhere=' WHERE d.STATUS=1 AND d.IS_DEL=1 AND d.IS_DELETE=0'
        .' AND d.STORE_ID=10001 AND d.WEIGHT>0 AND d.ENERGY>0'
        .' AND d.CATEGORY BETWEEN 1 AND 6 AND f.IS_DELETE=0'
        .' AND c.IS_DELETE=0 AND c.MINUTE_SORT IN ('
        .'1,2,3,4,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,'
        .'21,22,23,24,25,26,27,28,29,34,35,36)';
    $recipeJoin=' JOIN '.$t['recipes'].' r ON r.DISHES_UUID=d.UUID'
        .' AND r.MEAL_TIMES=1';
    $popularCondition=' AND EXISTS(SELECT 1 FROM '.$t['date_menu'].' dm'
        .' WHERE dm.DISHES_UUID=r.DISHES_UUID'
        .' AND dm.MENU_DATE=r.MENU_DATE'
        .' AND dm.RESTAURANT_ID=r.RESTAURANT_ID'
        .' AND dm.MEAL_TIMES=1 AND dm.MEAL_TYPE=3)';
    $candidateQueries=[
        'actual_menu'=>$candidatePrefix.$recipeJoin.$candidateWhere
            .$popularCondition.' ORDER BY d.ID DESC '.$limit(200),
        'recipe'=>$candidatePrefix.$recipeJoin.$candidateWhere
            .' ORDER BY d.ID DESC '.$limit(200),
        'dish_food'=>$candidatePrefix.$candidateWhere
            .' ORDER BY d.ID DESC '.$limit(200),
    ];
    foreach ($candidateQueries as $tier=>$sql) {
        $candidates=cdxCfRoRows($db,$sql);
        foreach ($candidates as $row) {
            $dName=(string)$row['d_name'];
            $fName=(string)$row['f_name'];
            if (!cdxCfRoSafeName($dName) || !cdxCfRoSafeName($fName)) continue;
            $sameDish=cdxCfRoCount($db,'SELECT COUNT(*) N FROM '.$t['dishes']
                .' WHERE NAME=? AND STATUS=1 AND IS_DEL=1 AND STORE_ID=10001',[$dName]);
            $like='%'.$fName.'%';
            $foodMatches=cdxCfRoCount($db,'SELECT COUNT(*) N FROM '
                .$t['foods_composition'].' WHERE NAME LIKE ? OR SUB_NAME LIKE ?',
                [$like,$like]);
            if ($sameDish!==1 || $foodMatches<1 || $foodMatches>100) continue;
            $dish=$row;
            $result['cases'][]=['phase'=>'candidate_tier','tier'=>$tier,
                'bounded_scan_rows'=>count($candidates)];
            break 2;
        }
    }
    if ($dish===null) {
        foreach (['/p/cater/select','/p/cater/selectAll','/p/cater/find',
            '/p/food/lists','/p/food/readById','/p/food/is_use_food',
            '/p/restaurant/richness','/p/restaurant/foodStructure',
            '/p/restaurant/nutrition','/p/restaurant/daypopularity',
            '/p/restaurant/morepopularity'] as $route) {
            $result['cases'][]=['route'=>$route,'status'=>'data-blocked',
                'reason'=>'no safe existing dish-component-food relation'];
        }
    } else {
        $dishUuid=(string)$dish['d_uuid'];
        $dishName=(string)$dish['d_name'];
        $foodId=(int)$dish['f_id'];
        $foodName=(string)$dish['f_name'];
        $result['cases'][]=['phase'=>'source_selected',
            'dish_uuid_sha12'=>substr(hash('sha256',$dishUuid),0,12),
            'food_id_sha12'=>substr(hash('sha256',(string)$foodId),0,12),
            'dish_category_type'=>gettype($dish['d_category']),
            'has_component'=>true];

        if ($tier==='actual_menu') {
            $preferred=cdxCfRoRows($db,
                'SELECT r.MENU_DATE R_DATE,r.RESTAURANT_ID R_RESTAURANT,'
                .'r.TYPE R_TYPE FROM '.$t['recipes'].' r'
                .' WHERE r.DISHES_UUID=? AND r.MEAL_TIMES=1'
                .' AND EXISTS(SELECT 1 FROM '.$t['date_menu'].' dm'
                .' WHERE dm.DISHES_UUID=r.DISHES_UUID'
                .' AND dm.MENU_DATE=r.MENU_DATE'
                .' AND dm.RESTAURANT_ID=r.RESTAURANT_ID'
                .' AND dm.MEAL_TIMES=1 AND dm.MEAL_TYPE=3)'
                .' ORDER BY r.MENU_DATE DESC '.$limit(1),[$dishUuid]);
            if ($preferred) {
                $pDay=substr((string)$preferred[0]['r_date'],0,10);
                $pRestaurant=(int)$preferred[0]['r_restaurant'];
                $pType=(int)$preferred[0]['r_type'];
                if (preg_match('/^\d{4}-\d{2}-\d{2}$/',$pDay)
                    && $pRestaurant>0 && $pType>=1 && $pType<=6) {
                    $popular=['day'=>$pDay,'restaurant_id'=>$pRestaurant,
                        'type'=>$pType];
                    $recipe=$popular;
                }
            }
        }

        $recipeRows=cdxCfRoRows($db,'SELECT r.MENU_DATE R_DATE,'
            .' r.RESTAURANT_ID R_RESTAURANT,r.TYPE R_TYPE'
            .' FROM '.$t['recipes'].' r WHERE r.DISHES_UUID=?'
            .' AND r.MEAL_TIMES=1 ORDER BY r.MENU_DATE DESC '.$limit(50),
            [$dishUuid]);
        foreach ($recipeRows as $row) {
            $day=substr((string)$row['r_date'],0,10);
            $restaurantId=(int)$row['r_restaurant'];
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/',$day)
                || $restaurantId<=0 || (int)$row['r_type']<1
                || (int)$row['r_type']>6) continue;
            if ($recipe===null) {
                $recipe=['day'=>$day,'restaurant_id'=>$restaurantId,
                    'type'=>(int)$row['r_type']];
            }
            if (cdxCfRoCount($db,'SELECT COUNT(*) N FROM '.$t['date_menu']
                .' WHERE DISHES_UUID=? AND MENU_DATE=? AND RESTAURANT_ID=?'
                .' AND MEAL_TIMES=1 AND MEAL_TYPE=3',
                [$dishUuid,$day,$restaurantId])>0) {
                $popular=['day'=>$day,'restaurant_id'=>$restaurantId,
                    'type'=>(int)$row['r_type']];
                break;
            }
        }
        $result['cases'][]=['phase'=>'analysis_source',
            'recipe_relation'=>$recipe!==null,
            'actual_date_menu_relation'=>$popular!==null,
            'date_sha12'=>$recipe!==null
                ? substr(hash('sha256',$recipe['day']),0,12):null];

        $phase='type1_permission_fixture';
        $now=date('Y-m-d H:i:s');
        $db->prepare('INSERT INTO '.$t['user']
            .' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,STATUS,CREATE_TIME,CREATE_BY,IS_DELETE)'
            .' VALUES (?,?,?,?,?,1,1,?,?,0)')
            ->execute([$userUuid,$marker,'DISABLED',
                strtoupper(bin2hex(random_bytes(8))),$marker,$now,'CODEX']);
        $users=cdxCfRoRows($open(),'SELECT ID,TYPE FROM '.$t['user']
            .' WHERE UUID=? AND ACCOUNT=?',[$userUuid,$marker]);
        if (count($users)!==1 || (int)$users[0]['type']!==1) {
            throw new RuntimeException('Synthetic type1 actor readback failed');
        }
        $userId=(int)$users[0]['id'];
        require_once $root.'/application/base/Security.php';
        $token=\app\base\Security::encrypt(implode("\t",[
            (string)$userId,$userUuid,$marker,'1','','',$marker,
        ]));
        if (!is_string($token) || $token==='') {
            throw new RuntimeException('Synthetic token unavailable');
        }
        $denied=cdxCfRoPost($base,'/p/cater/select',
            ['keyword'=>$dishName,'page'=>1],$token);
        if (($denied['code']??null)!==1) {
            throw new RuntimeException('No-menu type1 negative failed');
        }
        $db->prepare('INSERT INTO '.$t['role']
            .' (UUID,NAME,STATUS,CREATE_TIME,CREATE_BY) VALUES (?,?,1,?,?)')
            ->execute([$roleUuid,$marker,$now,'CODEX']);
        $roles=cdxCfRoRows($open(),'SELECT ID FROM '.$t['role']
            .' WHERE UUID=? AND NAME=?',[$roleUuid,$marker]);
        if (count($roles)!==1) throw new RuntimeException('Synthetic role readback failed');
        $roleId=(int)$roles[0]['id'];
        $db->prepare('INSERT INTO '.$t['user_role']
            .' (USER_UUID,ROLE_UUID,CREATE_TIME,CREATE_BY) VALUES (?,?,?,?)')
            ->execute([$userUuid,$roleUuid,$now,$marker]);
        foreach ($menus as $menu) {
            $db->prepare('INSERT INTO '.$t['role_menu']
                .' (ROLE_UUID,MENU_UUID,CREATE_TIME,CREATE_BY) VALUES (?,?,?,?)')
                ->execute([$roleUuid,$menu[0],$now,$marker]);
        }
        if (cdxCfRoCount($open(),'SELECT COUNT(*) N FROM '.$t['role_menu']
            .' WHERE ROLE_UUID=? AND CREATE_BY=?',[$roleUuid,$marker])!==4) {
            throw new RuntimeException('Own permission links not exact');
        }
        $result['cases'][]=['phase'=>'type1_permission_fixture',
            'no_menu_code'=>1,'read_menu_links'=>4];

        // All endpoint requests below are SELECT-only codepaths. No full response is logged.
        $phase='formal_reads';
        $json=cdxCfRoPost($base,'/p/cater/select',
            ['keyword'=>$dishName,'status'=>1,'page'=>1],$token);
        $row=cdxCfRoFindById($json['data']['rows']??[],'uuid',$dishUuid);
        $result['cases'][]=['route'=>'/p/cater/select',
            'status'=>($json['code']??null)===0&&$row!==null?'tested-pass':'tested-fail',
            'own_row_types'=>$row?array_map('gettype',$row):[]];
        $json=cdxCfRoPost($base,'/p/cater/selectAll',
            ['keyword'=>$dishName,'status'=>1,'simple'=>0]);
        $row=cdxCfRoFindById($json['data']['rows']??[],'uuid',$dishUuid);
        $result['cases'][]=['route'=>'/p/cater/selectAll',
            'status'=>($json['code']??null)===0&&$row!==null?'tested-pass':'tested-fail',
            'own_row_types'=>$row?array_map('gettype',$row):[]];
        $json=cdxCfRoPost($base,'/p/cater/find',
            ['uuid'=>$dishUuid,'flag'=>1],$token);
        $item=$json['data']??null;
        $result['cases'][]=['route'=>'/p/cater/find',
            'status'=>($json['code']??null)===0
                &&is_array($item)&&($item['uuid']??null)===$dishUuid
                ?'tested-pass':'tested-fail',
            'own_row_types'=>is_array($item)?array_map('gettype',$item):[]];
        $json=cdxCfRoPost($base,'/p/food/lists',
            ['name'=>$foodName,'page'=>1,'pagesize'=>100],$token);
        $row=cdxCfRoFindById($json['data']['rows']??[],'id',(string)$foodId);
        $result['cases'][]=['route'=>'/p/food/lists',
            'status'=>($json['code']??null)===0&&$row!==null?'tested-pass':'tested-fail',
            'own_row_types'=>$row?array_map('gettype',$row):[]];
        $json=cdxCfRoPost($base,'/p/food/readById',['id'=>$foodId],$token);
        $item=$json['data']??null;
        $result['cases'][]=['route'=>'/p/food/readById',
            'status'=>($json['code']??null)===0
                &&is_array($item)&&(int)($item['id']??0)===$foodId
                ?'tested-pass':'tested-fail',
            'own_row_types'=>is_array($item)?array_map('gettype',$item):[]];
        $json=cdxCfRoPost($base,'/p/food/is_use_food',['id'=>$foodId],$token);
        $sourceRefs=cdxCfRoCount($open(),'SELECT COUNT(*) N FROM '.$t['dish']
            .' WHERE DISHES_UUID=? AND COMPOSITION_ID=?',[$dishUuid,$foodId]);
        $result['cases'][]=['route'=>'/p/food/is_use_food',
            'status'=>($json['code']??null)===1&&$sourceRefs>0
                ?'tested-pass':'tested-fail',
            'expected_occupied_code'=>1,'source_refs'=>$sourceRefs];

        $restaurantRoutes=['/p/restaurant/richness',
            '/p/restaurant/foodStructure','/p/restaurant/nutrition'];
        foreach ($restaurantRoutes as $route) {
            if ($recipe===null) {
                $result['cases'][]=['route'=>$route,'status'=>'data-blocked',
                    'reason'=>'no existing recipe for selected dish'];
                continue;
            }
            $form=['start_time'=>$recipe['day'],'end_time'=>$recipe['day'],
                'meal_times'=>1,'restaurant_id'=>$recipe['restaurant_id']];
            if ($route==='/p/restaurant/nutrition') $form['status']=1;
            $json=cdxCfRoPost($base,$route,$form);
            $data=$json['data']??null;
            $expectedCount=$route==='/p/restaurant/richness'
                ? cdxCfRoCount($open(),
                    'SELECT COUNT(DISTINCT r.DISHES_UUID) N FROM '.$t['recipes'].' r'
                    .' JOIN '.$t['dishes'].' d ON d.UUID=r.DISHES_UUID'
                    .' WHERE r.MENU_DATE=? AND r.RESTAURANT_ID=?'
                    .' AND r.MEAL_TIMES=1 AND d.STATUS=1 AND d.IS_DEL=1',
                    [$recipe['day'],$recipe['restaurant_id']]) : 0;
            $ownPresent=$route==='/p/restaurant/richness'
                ? is_array($data)&&isset($data['map'],$data['datex'],$data['table'])
                    && cdxCfRoNumericTotal($data['map'])>0
                    && (int)cdxCfRoNumericTotal($data['map'])===$expectedCount
                : cdxCfRoContains($data,$dishName);
            $result['cases'][]=['route'=>$route,
                'status'=>($json['code']??null)===0&&$ownPresent
                    ?'tested-pass':'tested-fail',
                'nonempty_target'=>$ownPresent,
                'source_distinct_dishes'=>$route==='/p/restaurant/richness'
                    ? $expectedCount:null,
                'data_types'=>is_array($data)?array_map('gettype',$data):[]];
        }
        foreach (['/p/restaurant/daypopularity',
            '/p/restaurant/morepopularity'] as $route) {
            if ($popular===null) {
                $result['cases'][]=['route'=>$route,'status'=>'data-blocked',
                    'reason'=>'no existing recipe plus actual date-menu relation'];
                continue;
            }
            $form=$route==='/p/restaurant/daypopularity'
                ? ['restaurant_id'=>$popular['restaurant_id'],
                    'menu_date'=>$popular['day'],'meal_times'=>1]
                : ['restaurant_id'=>$popular['restaurant_id'],
                    'start_time'=>$popular['day'],'end_time'=>$popular['day'],
                    'meal_times'=>1];
            $json=cdxCfRoPost($base,$route,$form);
            $data=$json['data']??null;
            if ($route==='/p/restaurant/daypopularity') {
                $index=is_array($data['x']??null)
                    ? array_search($dishName,$data['x'],true):false;
                $ownPresent=$index!==false
                    && is_numeric($data['map_val'][$index]??null)
                    && (float)$data['map_val'][$index]>0;
            } else {
                $own=cdxCfRoFindById($data['table']??[],'name',$dishName);
                $ownPresent=$own!==null
                    && is_numeric($own['popularity']??null)
                    && (float)$own['popularity']>0;
            }
            $result['cases'][]=['route'=>$route,
                'status'=>($json['code']??null)===0&&$ownPresent
                    ?'tested-pass':'tested-fail',
                'nonempty_target'=>$ownPresent,
                'data_types'=>is_array($data)?array_map('gettype',$data):[]];
        }
        $still=cdxCfRoCount($open(),'SELECT COUNT(*) N FROM '.$t['dishes'].' d'
            .' JOIN '.$t['dish'].' c ON c.DISHES_UUID=d.UUID'
            .' JOIN '.$t['foods_composition'].' f ON f.ID=c.COMPOSITION_ID'
            .' WHERE d.UUID=? AND c.ID=? AND f.ID=? AND d.NAME=? AND f.NAME=?'
            .' AND d.STATUS=1 AND d.IS_DEL=1 AND d.STORE_ID=10001',
            [$dishUuid,(int)$dish['c_id'],$foodId,$dishName,$foodName]);
        $result['cases'][]=['phase'=>'post_http_source_readback',
            'same_existing_relation'=>$still===1];
        if ($still!==1) {
            throw new RuntimeException('Shared read-only source changed during test');
        }
    }
} catch (Throwable $error) {
    $result['failure']=['phase'=>$phase,'class'=>get_class($error),
        'code'=>$error->getCode()];
} finally {
    try {
        $cleanup=$open();
        $links=cdxCfRoRows($cleanup,'SELECT ID,MENU_UUID,CREATE_BY FROM '
            .$t['role_menu'].' WHERE ROLE_UUID=?',[$roleUuid]);
        if (count($links)>count($menus)) {
            throw new RuntimeException('Role-menu cardinality unsafe');
        }
        $allowed=array_column($menus,0);
        foreach ($links as $link) {
            if (!in_array((string)$link['menu_uuid'],$allowed,true)
                || (string)$link['create_by']!==$marker) {
                throw new RuntimeException('Unknown role-menu link; refuse broad delete');
            }
            $stmt=$cleanup->prepare('DELETE FROM '.$t['role_menu']
                .' WHERE ID=? AND ROLE_UUID=? AND MENU_UUID=? AND CREATE_BY=?');
            $stmt->execute([(int)$link['id'],$roleUuid,
                (string)$link['menu_uuid'],$marker]);
            if ($stmt->rowCount()!==1) throw new RuntimeException('Role-menu exact delete failed');
            $result['cleanup']['role_menu_deleted']=
                ($result['cleanup']['role_menu_deleted']??0)+1;
        }
        if (cdxCfRoCount($cleanup,'SELECT COUNT(*) N FROM '.$t['user_role']
            .' WHERE ROLE_UUID=? AND USER_UUID<>?',[$roleUuid,$userUuid])!==0) {
            throw new RuntimeException('Other user linked test role; refuse cleanup');
        }
        $stmt=$cleanup->prepare('DELETE FROM '.$t['user_role']
            .' WHERE USER_UUID=? AND ROLE_UUID=? AND CREATE_BY=?');
        $stmt->execute([$userUuid,$roleUuid,$marker]);
        $result['cleanup']['user_role_deleted']=$stmt->rowCount();
        if ($roleId<=0) {
            $found=cdxCfRoRows($cleanup,'SELECT ID FROM '.$t['role']
                .' WHERE UUID=? AND NAME=? AND CREATE_BY=?',
                [$roleUuid,$marker,'CODEX']);
            if (count($found)>1) throw new RuntimeException('Role cardinality unsafe');
            $roleId=$found?(int)$found[0]['id']:0;
        }
        if ($userId<=0) {
            $found=cdxCfRoRows($cleanup,'SELECT ID FROM '.$t['user']
                .' WHERE UUID=? AND ACCOUNT=? AND TYPE=1',[$userUuid,$marker]);
            if (count($found)>1) throw new RuntimeException('User cardinality unsafe');
            $userId=$found?(int)$found[0]['id']:0;
        }
        if ($roleId>0) {
            $stmt=$cleanup->prepare('DELETE FROM '.$t['role']
                .' WHERE ID=? AND UUID=? AND NAME=? AND CREATE_BY=?');
            $stmt->execute([$roleId,$roleUuid,$marker,'CODEX']);
            if ($stmt->rowCount()!==1) throw new RuntimeException('Role exact delete failed');
            $result['cleanup']['role_deleted']=1;
        }
        if ($userId>0) {
            $stmt=$cleanup->prepare('DELETE FROM '.$t['user']
                .' WHERE ID=? AND UUID=? AND ACCOUNT=? AND TYPE=1');
            $stmt->execute([$userId,$userUuid,$marker]);
            if ($stmt->rowCount()!==1) throw new RuntimeException('User exact delete failed');
            $result['cleanup']['user_deleted']=1;
        }
        $result['cleanup']['user_zero']=cdxCfRoCount($cleanup,
            'SELECT COUNT(*) N FROM '.$t['user'].' WHERE UUID=? OR ACCOUNT=?',
            [$userUuid,$marker])===0;
        $result['cleanup']['role_zero']=cdxCfRoCount($cleanup,
            'SELECT COUNT(*) N FROM '.$t['role'].' WHERE UUID=? OR NAME=?',
            [$roleUuid,$marker])===0;
        $result['cleanup']['links_zero']=cdxCfRoCount($cleanup,
            'SELECT COUNT(*) N FROM '.$t['role_menu'].' WHERE ROLE_UUID=?',
            [$roleUuid])===0
            &&cdxCfRoCount($cleanup,'SELECT COUNT(*) N FROM '.$t['user_role']
                .' WHERE USER_UUID=? AND ROLE_UUID=?',[$userUuid,$roleUuid])===0;
    } catch (Throwable $error) {
        $result['cleanup_error']=['class'=>get_class($error),
            'code'=>$error->getCode()];
    }
}
echo json_encode($result,JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT).PHP_EOL;
if (isset($result['failure']) || isset($result['cleanup_error'])
    || empty($result['cleanup']['user_zero'])
    || empty($result['cleanup']['role_zero'])
    || empty($result['cleanup']['links_zero'])) exit(1);
foreach ($result['cases'] as $case) {
    if (($case['status']??'')==='tested-fail') exit(1);
}
