<?php

declare(strict_types=1);

$lane = (string)getenv('CDX_GHX_LANE');
$mode = (string)($argv[1] ?? '');
$selfSha = (string)hash_file('sha256', __FILE__);
if (!in_array($lane, ['B', 'C'], true)
    || count($argv) !== 2
    || !in_array($mode, ['--preflight', '--execute', '--audit'], true)
    || ($mode === '--execute'
        && ((string)getenv('CDX_GHX_APPROVE') !== 'ROOT_GROUP_EXPORT_V1'
            || !hash_equals($selfSha, (string)getenv('CDX_GHX_SHA'))))) {
    echo json_encode(['status'=>'default_deny','connected'=>false,
        'http_sent'=>false,'dml'=>false]), PHP_EOL;
    exit(78);
}

$stage = 'bootstrap';
set_exception_handler(static function (Throwable $error) use (&$stage): void {
    echo json_encode(['status'=>'redacted_stop','stage'=>$stage,
        'class'=>get_class($error),'code'=>(string)$error->getCode(),
        'message_sha'=>hash('sha256',$error->getMessage()),
        'recovery'=>'inspect_private_journal_if_present']), PHP_EOL;
    exit(2);
});

[$root,$pid,$port,$dialect] = [
    'B'=>['/tmp/zhct-http-store-mysql.dQTTBP',10001,18081,'mysql'],
    'C'=>['/tmp/zhct-dm-poc-20260928.fcpiiN/store',112175,18080,'dm'],
][$lane];
$sourceHashes = [
    '/application/p/controller/ZhaikerHealth.php'=>'078c936a8a2aabf3f748fd9e89af3a38b9fa01dc536ffb2047a90c7132fbd167',
    '/application/p/logic/ZhaikerHealth.php'=>'f2c8b6eea8b502c411c00b92dbeed819c91556be8ed472bea25e5ed532503c59',
    '/application/model/Excel.php'=>'468f4d22f73e0bf3e6e5fac3551c690f0aafb60678b4ec38ca88ce60afad52d0',
];
if (PHP_VERSION_ID < 70300 || PHP_VERSION_ID >= 70400 || is_link($root)) {
    throw new RuntimeException('Runtime/root drift');
}
foreach ($sourceHashes as $path=>$sha) {
    if (!is_file($root.$path) || hash_file('sha256',$root.$path) !== $sha) {
        throw new RuntimeException('Business source drift');
    }
}
if (!is_file($root.'/vendor/autoload.php')
    || !is_file($root.'/application/base/Security.php')) {
    throw new RuntimeException('Export runtime missing');
}

$stage = 'http_boundary';
$base = 'http://127.0.0.1:'.$port;
$cmdline = (string)@file_get_contents('/proc/'.$pid.'/cmdline');
$childrenPath = '/proc/'.$pid.'/task/'.$pid.'/children';
$boundary = json_decode((string)@file_get_contents($base.'/__poc_boundary'),true);
if (strpos($cmdline,$root.'/public') === false
    || strpos($cmdline,'127.0.0.1:'.$port) === false
    || trim((string)@file_get_contents($childrenPath)) !== ''
    || !is_array($boundary) || ($boundary['external_stubs']??null)!==true
    || ($boundary['redis_isolated']??null)!==true
    || ($boundary['real_queue']??true)!==false) {
    throw new RuntimeException('Isolated HTTP boundary drift');
}
$runtimeDir = $root.'/runtime';
if (!is_dir($runtimeDir) || is_link($runtimeDir) || realpath($runtimeDir)!==$runtimeDir) {
    throw new RuntimeException('Private runtime output directory unavailable');
}

$stage = 'database_boundary';
$runtime = require $root.'/application/database.php';
$aiRuntime = $runtime['ai_zhct']??null;
if (!is_array($runtime) || !is_array($aiRuntime)
    || (string)($runtime['prefix']??'')!=='ydy_'
    || (string)($aiRuntime['prefix']??'')!=='yoshop_') {
    throw new RuntimeException('Store/AI config missing');
}
if ($dialect==='mysql') {
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    $localAi = $local['ai_zhct']??null;
    if (!is_array($localAi)) throw new RuntimeException('Code local AI config missing');
    foreach (['hostname','database','username','password'] as $key) {
        if (($runtime[$key]??null)!==($local[$key]??null)
            || ($aiRuntime[$key]??null)!==($localAi[$key]??null)) {
            throw new RuntimeException('Not code-local Store/AI MySQL');
        }
    }
    if ((int)($runtime['hostport']??3306)!==(int)($local['hostport']??3306)
        || (int)($aiRuntime['hostport']??3306)
            !==(int)($localAi['hostport']??$local['hostport']??3306)) {
        throw new RuntimeException('Code local port drift');
    }
    $storeDsn='mysql:host='.$runtime['hostname'].';port='.(int)($runtime['hostport']??3306)
        .';dbname='.$runtime['database'].';charset='.(string)($runtime['charset']??'utf8');
    $aiDsn='mysql:host='.$aiRuntime['hostname'].';port='.(int)($aiRuntime['hostport']??3306)
        .';dbname='.$aiRuntime['database'].';charset='.(string)($runtime['charset']??'utf8');
} else {
    foreach (['hostname'=>'POC_HOST','hostport'=>'POC_PORT','database'=>'POC_SCHEMA',
        'username'=>'POC_USER','password'=>'POC_PASSWORD'] as $key=>$env) {
        if ((string)($runtime[$key]??'')!==(string)getenv($env)) {
            throw new RuntimeException('C Store worker config drift');
        }
    }
    foreach (['hostname'=>'POC_AI_HOST','hostport'=>'POC_AI_PORT','database'=>'POC_AI_SCHEMA',
        'username'=>'POC_AI_USER','password'=>'POC_AI_PASSWORD'] as $key=>$env) {
        if ((string)($aiRuntime[$key]??'')!==(string)getenv($env)) {
            throw new RuntimeException('C AI worker config drift');
        }
    }
    if ((string)getenv('POC_HOST')!=='127.0.0.1' || (string)getenv('POC_PORT')!=='5237'
        || strtoupper((string)getenv('POC_SCHEMA'))!=='ZHCT_APP'
        || (string)getenv('POC_AI_HOST')!=='127.0.0.1'
        || (string)getenv('POC_AI_PORT')!=='5238'
        || strtoupper((string)getenv('POC_AI_SCHEMA'))!=='AIZHCT_APP') {
        throw new RuntimeException('Not approved nonproduction DM pair');
    }
    $storeDsn='dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
    $aiDsn='dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP';
}
$connect = static function (string $dsn,array $config): PDO {
    return new PDO($dsn,(string)$config['username'],(string)$config['password'],[
        PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER,
        PDO::ATTR_TIMEOUT=>5]);
};
$store=$connect($storeDsn,$runtime); $ai=$connect($aiDsn,$aiRuntime);
$identity = static function (PDO $db,string $which) use ($dialect): string {
    if ($dialect==='mysql') return (string)$db->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")->fetchColumn();
    $schema=(string)$db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();
    return ($which==='store'?'ZHCT_APP|5237|':'AIZHCT_APP|5238|').$schema;
};
$approvedStore=$dialect==='mysql'
    ?'003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401'
    :'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194';
$approvedAi=$dialect==='mysql'
    ?'210f925e093f71e26a8c8923f0f626e3be0a15ccff3d386bba21feef695c8a5b'
    :'628c12663da929e593de74c9c58e5bcfab0530fbfbcb1d90a9ed7b736a45fd5e';
if (!hash_equals($approvedStore,hash('sha256',$identity($store,'store')))
    || !hash_equals($approvedAi,hash('sha256',$identity($ai,'ai')))) {
    throw new RuntimeException('Approved DB pair identity drift');
}
$s=$dialect==='dm'?'YDY_':'ydy_'; $a=$dialect==='dm'?'YOSHOP_':'yoshop_';
$storeTables=['health_metric','measurement_session','health_record','measurement_session_raw'];
$requiredStore=[
    'health_metric'=>['METRIC_CODE','METRIC_NAME','UNIT'],
    'measurement_session'=>['SESSION_ID','AI_USER_ID','EVENT_TYPE','START_TIME','SOURCE_PLATFORM','VENDOR','DEVICE_TYPE','DEVICE_MODEL','BUSINESS_CONTEXT','IS_VALID'],
    'health_record'=>['RECORD_ID','SESSION_ID','AI_USER_ID','METRIC_CODE','VALUE','UNIT','SEGMENT','SEGMENT_KEY','START_TIME'],
    'measurement_session_raw'=>['SESSION_ID','DATA'],
];
$requiredAi=['USER_ID','STORE_ID','MOBILE','NICK_NAME','BALANCE','GIFT_BALANCE','IS_DELETE','CREATE_TIME'];
$metadata=[];
$columnRows = static function (PDO $db,string $physical,string $schema) use ($dialect): array {
    if ($dialect==='mysql') {
        $q=$db->prepare('SELECT COLUMN_NAME,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,COLUMN_DEFAULT,EXTRA'
            .' FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION');
        $q->execute([$physical]); return $q->fetchAll(PDO::FETCH_ASSOC);
    }
    $q=$db->prepare('SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,NULLABLE,DATA_DEFAULT'
        .' FROM ALL_TAB_COLUMNS WHERE OWNER=? AND TABLE_NAME=? ORDER BY COLUMN_ID');
    $q->execute([$schema,$physical]); return $q->fetchAll(PDO::FETCH_ASSOC);
};
foreach ($storeTables as $logical) {
    $rows=$columnRows($store,$s.$logical,'ZHCT_APP'); if ($rows===[]) throw new RuntimeException('Store table absent');
    $names=[]; foreach ($rows as $row) {$names[]=strtoupper((string)$row['column_name']);$metadata[]=['store',$logical,$row];}
    foreach ($requiredStore[$logical] as $name) if (!in_array($name,$names,true)) throw new RuntimeException('Store column absent');
}
$aiRows=$columnRows($ai,$a.'user','AIZHCT_APP'); if ($aiRows===[]) throw new RuntimeException('AI user table absent');
$aiNames=[]; foreach ($aiRows as $row) {$aiNames[]=strtoupper((string)$row['column_name']);$metadata[]=['ai','user',$row];}
foreach ($requiredAi as $name) if (!in_array($name,$aiNames,true)) throw new RuntimeException('AI user column absent');

$stage='metadata_side_effect_gate';
$mutatedStore=[$s.'measurement_session',$s.'health_record'];
if ($dialect==='mysql') {
    $marks='?,?';
    $q=$store->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN ('.$marks.')');
    $q->execute($mutatedStore); $triggerStore=(int)$q->fetchColumn();
    $q=$store->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=DATABASE()'
        .' AND (TABLE_NAME IN ('.$marks.') OR REFERENCED_TABLE_NAME IN ('.$marks.'))');
    $q->execute(array_merge($mutatedStore,$mutatedStore)); $fkStore=(int)$q->fetchColumn();
    $q=$ai->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE=?');
    $q->execute([$a.'user']); $triggerAi=(int)$q->fetchColumn();
    $q=$ai->prepare('SELECT COUNT(*) FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS WHERE CONSTRAINT_SCHEMA=DATABASE()'
        .' AND (TABLE_NAME=? OR REFERENCED_TABLE_NAME=?)');
    $q->execute([$a.'user',$a.'user']); $fkAi=(int)$q->fetchColumn();
} else {
    $triggerStore=(int)$store->query("SELECT COUNT(*) FROM ALL_TRIGGERS WHERE OWNER='ZHCT_APP'"
        ." AND TABLE_NAME IN ('YDY_MEASUREMENT_SESSION','YDY_HEALTH_RECORD')")->fetchColumn();
    $fkStore=(int)$store->query("SELECT COUNT(*) FROM ALL_CONSTRAINTS WHERE OWNER='ZHCT_APP' AND CONSTRAINT_TYPE='R'"
        ." AND TABLE_NAME IN ('YDY_MEASUREMENT_SESSION','YDY_HEALTH_RECORD')")->fetchColumn();
    $triggerAi=(int)$ai->query("SELECT COUNT(*) FROM ALL_TRIGGERS WHERE OWNER='AIZHCT_APP' AND TABLE_NAME='YOSHOP_USER'")->fetchColumn();
    $fkAi=(int)$ai->query("SELECT COUNT(*) FROM ALL_CONSTRAINTS WHERE OWNER='AIZHCT_APP' AND CONSTRAINT_TYPE='R'"
        ." AND TABLE_NAME='YOSHOP_USER'")->fetchColumn();
}
if ($triggerStore!==0 || $fkStore!==0 || $triggerAi!==0 || $fkAi!==0) {
    throw new RuntimeException('Unreviewed trigger or foreign key');
}
$metric=$store->prepare('SELECT METRIC_NAME,UNIT FROM '.$s.'health_metric WHERE METRIC_CODE=?');
$metric->execute(['weight']); $metricRow=$metric->fetch(PDO::FETCH_ASSOC);
if (!is_array($metricRow) || (string)$metricRow['metric_name']==='') throw new RuntimeException('Weight metric absent');
$day1='2001-01-02';$day2='2001-01-03';$time1=$day1.' 10:00:00';$time2=$day2.' 10:00:00';
$natural=$store->prepare('SELECT COUNT(*) FROM '.$s.'measurement_session'
    .' WHERE VENDOR=? AND DEVICE_TYPE=? AND BUSINESS_CONTEXT=? AND IS_VALID=1 AND START_TIME>=? AND START_TIME<=?');
$natural->execute(['zhaiker','body_scale','health',$day1.' 00:00:00',$day2.' 23:59:59']);
$naturalCount=(int)$natural->fetchColumn();
$oldStore=(int)$store->query('SELECT COUNT(*) FROM '.$s."measurement_session WHERE DEVICE_MODEL LIKE 'CDXGHX%'")->fetchColumn();
$oldAi=(int)$ai->query('SELECT COUNT(*) FROM '.$a."user WHERE NICK_NAME LIKE 'CDXGHX%' OR MOBILE LIKE 'CDXGHX%'")->fetchColumn();
require_once $root.'/vendor/autoload.php';
$reader=\PHPExcel_IOFactory::createReader('Excel5');
if (!is_object($reader)) throw new RuntimeException('Excel5 reader unavailable');

$journal=(string)getenv('CDX_GHX_JOURNAL');
$countMarkers=static function (PDO $storeDb,PDO $aiDb) use ($s,$a): int {
    return (int)$storeDb->query('SELECT COUNT(*) FROM '.$s."measurement_session WHERE DEVICE_MODEL LIKE 'CDXGHX%'")->fetchColumn()
        +(int)$aiDb->query('SELECT COUNT(*) FROM '.$a."user WHERE NICK_NAME LIKE 'CDXGHX%' OR MOBILE LIKE 'CDXGHX%'")->fetchColumn();
};
if ($mode==='--audit') {
    $stage='independent_audit';
    if (!preg_match('~^/tmp/cdx-ghx-[a-f0-9]{12}/recovery\.json$~D',$journal)
        || !is_file($journal) || is_link($journal) || is_link(dirname($journal))
        || (fileperms(dirname($journal))&0777)!==0700 || (fileperms($journal)&0777)!==0600) {
        throw new RuntimeException('Audit journal invalid');
    }
    $saved=json_decode((string)file_get_contents($journal),true);
    if (!is_array($saved) || ($saved['lane']??null)!==$lane
        || ($saved['phase']??null)!=='fixture_deleted'
        || !preg_match('/^CDXGHX[A-F0-9]{12}$/D',(string)($saved['marker']??''))) {
        throw new RuntimeException('Audit ownership invalid');
    }
    $freshStore=$connect($storeDsn,$runtime);$freshAi=$connect($aiDsn,$aiRuntime);
    if ($countMarkers($freshStore,$freshAi)!==0) throw new RuntimeException('Fixture remains');
    foreach (glob($runtimeDir.'/CDXGHX*')?:[] as $path) {
        if (is_file($path)) throw new RuntimeException('Temporary XLS remains');
    }
    unlink($journal);rmdir(dirname($journal));
    echo json_encode(['status'=>'independent_zero_pass','lane'=>$lane,
        'marker_rows'=>0,'temp_files'=>0,'private_journal_removed'=>true]),PHP_EOL;
    exit(0);
}
if ($mode==='--preflight') {
    $ready=$naturalCount===0 && $oldStore===0 && $oldAi===0;
    echo json_encode(['status'=>$ready?'select_only_ready':'source_or_marker_stop','lane'=>$lane,
        'metadata_sha'=>hash('sha256',serialize($metadata)),'store_fk'=>$fkStore,'store_triggers'=>$triggerStore,
        'ai_fk'=>$fkAi,'ai_triggers'=>$triggerAi,'natural_window_rows'=>$naturalCount,
        'old_marker_rows'=>$oldStore+$oldAi,'excel5_reader'=>true,'http_sent'=>false,'dml'=>false]),PHP_EOL;
    exit($ready?0:2);
}
if ($naturalCount!==0 || $oldStore!==0 || $oldAi!==0) throw new RuntimeException('Source isolation gate failed');

$marker='CDXGHX'.strtoupper(bin2hex(random_bytes(6)));
$directory='/tmp/cdx-ghx-'.strtolower(bin2hex(random_bytes(6)));
if (file_exists($directory)||!mkdir($directory,0700)) throw new RuntimeException('Private directory unavailable');
chmod($directory,0700);$journal=$directory.'/recovery.json';
$saved=['lane'=>$lane,'marker'=>$marker,'phase'=>'before_insert','user_id'=>0,'session_ids'=>[],'record_ids'=>[]];
$save=static function () use (&$saved,$journal): void {
    if (file_put_contents($journal,json_encode($saved),LOCK_EX)===false || !chmod($journal,0600)) {
        throw new RuntimeException('Journal write failed');
    }
};
$save();
$results=[];$failure='';$httpUncertain=false;$fixtureCommitted=false;$tempFiles=[];
$send=static function (string $method,array $params,string $token='') use ($base,&$httpUncertain): array {
    $encoded=http_build_query($params,'','&',PHP_QUERY_RFC3986);
    $url=$base.'/p/zhaikerHealth/groupAnalysisExport'.($method==='GET'?'?'.$encoded:'');
    $headers=($token!==''?'token: '.$token."\r\n":'')
        .($method==='POST'?"Content-Type: application/x-www-form-urlencoded\r\n":'');
    $started=microtime(true);
    $stream=@fopen($url,'rb',false,stream_context_create(['http'=>[
        'method'=>$method,'header'=>$headers,'content'=>$method==='POST'?$encoded:'',
        'timeout'=>25,'ignore_errors'=>true]]));
    $body=$stream===false?false:stream_get_contents($stream,2097153);
    $responseHeaders=$http_response_header??[];if(is_resource($stream))fclose($stream);
    preg_match('/HTTP\/\S+\s+(\d+)/',(string)($responseHeaders[0]??''),$m);$http=(int)($m[1]??0);
    if($stream===false||$body===false||$http===0)$httpUncertain=true;
    $contentType='';$disposition='';
    foreach($responseHeaders as $line){if(stripos($line,'Content-Type:')===0)$contentType=$line;if(stripos($line,'Content-Disposition:')===0)$disposition=$line;}
    return ['http'=>$http,'body'=>$body,'content_type'=>$contentType,'disposition'=>$disposition,
        'elapsed_ms'=>(int)round((microtime(true)-$started)*1000)];
};
$parse=static function(array $response) use($runtimeDir,$marker,&$tempFiles):array{
    if($response['http']!==200||!is_string($response['body'])
        ||stripos($response['content_type'],'application/vnd.ms-excel')===false
        ||strpos($response['disposition'],$marker)===false
        ||substr($response['body'],0,8)!=="\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"){
        throw new RuntimeException('Excel5 response contract mismatch');
    }
    $file=tempnam($runtimeDir,'CDXGHX');
    if($file===false||strpos($file,$runtimeDir.'/CDXGHX')!==0)throw new RuntimeException('Temp XLS unavailable');
    chmod($file,0600);$tempFiles[]=$file;
    if(file_put_contents($file,$response['body'])!==strlen($response['body']))throw new RuntimeException('Temp XLS write failed');
    $book=\PHPExcel_IOFactory::createReader('Excel5')->load($file);$sheet=$book->getActiveSheet();
    $rows=$sheet->toArray(null,true,true,true);$types=[];
    foreach(['A1','B1','C1','A2','B2','C2','C4','C5'] as $cell)$types[$cell]=$sheet->getCell($cell)->getDataType();
    $book->disconnectWorksheets();
    return ['rows'=>$rows,'cell_types'=>$types,'bytes'=>strlen($response['body'])];
};
try{
    $stage='ai_user_insert';
    $ai->beginTransaction();
    $q=$ai->prepare('INSERT INTO '.$a.'user (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME) VALUES (10001,?,?,?,?,0,?)');
    $q->execute([$marker,$marker,'0.00','0.00',time()]);
    $find=$ai->prepare('SELECT USER_ID FROM '.$a.'user WHERE STORE_ID=10001 AND NICK_NAME=? AND MOBILE=?');
    $find->execute([$marker,$marker]);$ids=$find->fetchAll(PDO::FETCH_COLUMN);
    if(count($ids)!==1||(int)$ids[0]<=0)throw new RuntimeException('AI fixture ownership ambiguous');
    $saved['user_id']=(int)$ids[0];$ai->commit();$saved['phase']='ai_user_committed';$save();

    $stage='store_ai_user_collision_scan';
    if($dialect==='mysql'){
        $q=$store->prepare("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND COLUMN_NAME='ai_user_id' ORDER BY TABLE_NAME");$q->execute();
    }else{
        $q=$store->prepare("SELECT TABLE_NAME FROM ALL_TAB_COLUMNS WHERE OWNER='ZHCT_APP' AND COLUMN_NAME='AI_USER_ID' ORDER BY TABLE_NAME");$q->execute();
    }
    $preRefs=[];
    foreach($q->fetchAll(PDO::FETCH_COLUMN) as $physical){if(!preg_match('/^YDY_[A-Z0-9_]+$/i',(string)$physical))continue;
        $c=$store->prepare('SELECT COUNT(*) FROM '.$physical.' WHERE AI_USER_ID=?');$c->execute([$saved['user_id']]);
        $n=(int)$c->fetchColumn();if($n>0)$preRefs[strtoupper((string)$physical)]=$n;}
    if($preRefs!==[])throw new RuntimeException('New AI user id collides with Store rows');

    $stage='store_fixture_insert';$store->beginTransaction();
    foreach([[$time1,'70.50'],[$time2,'69.50']] as $fixture){[$when,$weight]=$fixture;
        $q=$store->prepare('INSERT INTO '.$s.'measurement_session (AI_USER_ID,EVENT_TYPE,START_TIME,SOURCE_PLATFORM,VENDOR,DEVICE_TYPE,DEVICE_MODEL,BUSINESS_CONTEXT,IS_VALID) VALUES (?,?,?,?,?,?,?,?,1)');
        $q->execute([$saved['user_id'],'measurement',$when,'direct_device','zhaiker','body_scale',$marker,'health']);
        $f=$store->prepare('SELECT SESSION_ID FROM '.$s.'measurement_session WHERE AI_USER_ID=? AND DEVICE_MODEL=? AND START_TIME=?');
        $f->execute([$saved['user_id'],$marker,$when]);$sessionIds=$f->fetchAll(PDO::FETCH_COLUMN);
        if(count($sessionIds)!==1||(int)$sessionIds[0]<=0)throw new RuntimeException('Session ownership ambiguous');
        $sessionId=(int)$sessionIds[0];$saved['session_ids'][]=$sessionId;
        $q=$store->prepare('INSERT INTO '.$s.'health_record (SESSION_ID,AI_USER_ID,METRIC_CODE,VALUE,UNIT,SEGMENT,SEGMENT_KEY,START_TIME) VALUES (?,?,?,?,?,NULL,?,?)');
        $q->execute([$sessionId,$saved['user_id'],'weight',$weight,'kg','all',$when]);
        $f=$store->prepare('SELECT RECORD_ID FROM '.$s.'health_record WHERE SESSION_ID=? AND AI_USER_ID=? AND METRIC_CODE=? AND START_TIME=?');
        $f->execute([$sessionId,$saved['user_id'],'weight',$when]);$recordIds=$f->fetchAll(PDO::FETCH_COLUMN);
        if(count($recordIds)!==1||(int)$recordIds[0]<=0)throw new RuntimeException('Record ownership ambiguous');
        $saved['record_ids'][]=(int)$recordIds[0];
    }
    $store->commit();$fixtureCommitted=true;$saved['phase']='fixture_committed';$save();
    $sourceQ=$store->prepare('SELECT HR.RECORD_ID,HR.SESSION_ID,HR.AI_USER_ID,HR.METRIC_CODE,HR.VALUE,HR.UNIT,HR.SEGMENT,HR.SEGMENT_KEY,HR.START_TIME,MS.EVENT_TYPE,MS.SOURCE_PLATFORM,MS.VENDOR,MS.DEVICE_TYPE,MS.DEVICE_MODEL,MS.BUSINESS_CONTEXT,MS.IS_VALID FROM '.$s.'health_record HR JOIN '.$s.'measurement_session MS ON MS.SESSION_ID=HR.SESSION_ID WHERE HR.AI_USER_ID=? AND MS.DEVICE_MODEL=? ORDER BY HR.START_TIME,HR.RECORD_ID');
    $sourceQ->execute([$saved['user_id'],$marker]);$source=$sourceQ->fetchAll(PDO::FETCH_ASSOC);
    if(count($source)!==2||bccomp((string)$source[0]['value'],'70.50',2)!==0||bccomp((string)$source[1]['value'],'69.50',2)!==0)throw new RuntimeException('Fixture source readback mismatch');
    $sourceSha=hash('sha256',serialize($source));
    $aiQ=$ai->prepare('SELECT USER_ID,STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME FROM '.$a.'user WHERE USER_ID=? AND NICK_NAME=?');
    $aiQ->execute([$saved['user_id'],$marker]);$aiSource=$aiQ->fetchAll(PDO::FETCH_ASSOC);
    if(count($aiSource)!==1)throw new RuntimeException('AI fixture readback mismatch');$aiSha=hash('sha256',serialize($aiSource));

    require_once $root.'/application/base/Security.php';
    $token=\app\base\Security::encrypt(implode("\t",['0',$marker,$marker,'0','','',$marker]));
    $params=['gender'=>'','age_min'=>'','age_max'=>'','weight_min'=>'','weight_max'=>'',
        'health_condition'=>'','metric_codes'=>'weight','start_date'=>$day1,'end_date'=>$day2,'file_name'=>$marker];
    foreach(['GET','POST'] as $method){$stage='unauth_'.$method;$unauth=$send($method,$params,'');
        $reject=is_string($unauth['body'])?json_decode($unauth['body'],true):null;
        if($unauth['http']!==200||!is_array($reject)||(int)($reject['code']??-1)!==100)throw new RuntimeException('Unauthenticated export not rejected');}
    $metricName=(string)$metricRow['metric_name'];$unit=(string)($metricRow['unit']??'');
    $header=$metricName.($unit!==''?'('.$unit.')':'');
    foreach(['GET','POST'] as $method){$stage='http_'.$method;$response=$send($method,$params,$token);$parsed=$parse($response);$rows=$parsed['rows'];
        $pass=count($rows)===5
            &&array_values($rows[1])===['姓名','日期',$header]
            &&($rows[2]['A']??null)==='用户'.$saved['user_id']&&($rows[2]['B']??null)===$day1
            &&bccomp((string)($rows[2]['C']??'0'),'70.50',2)===0
            &&($rows[3]['A']??null)==='用户'.$saved['user_id']&&($rows[3]['B']??null)===$day2
            &&bccomp((string)($rows[3]['C']??'0'),'69.50',2)===0
            &&($rows[4]['A']??null)==='团队平均值'&&($rows[4]['B']??null)==='-'
            &&bccomp((string)($rows[4]['C']??'0'),'70.00',2)===0
            &&($rows[5]['A']??null)==='团队标准差'&&($rows[5]['B']??null)==='-'
            &&bccomp((string)($rows[5]['C']??'0'),'0.71',2)===0;
        if(!$pass)throw new RuntimeException('Export cell contract mismatch');
        $normalized=$rows;$normalized[2]['A']='用户<ID>';$normalized[3]['A']='用户<ID>';
        $results[$method]=['status'=>'tested-pass','file_bytes'=>$parsed['bytes'],
            'content_type'=>'application/vnd.ms-excel','file_format'=>'Excel5/OLE2',
            'grid_sha'=>hash('sha256',serialize($normalized)),
            'cell_type_sha'=>hash('sha256',serialize($parsed['cell_types'])),
            'cell_types'=>$parsed['cell_types'],'elapsed_ms'=>$response['elapsed_ms']];
    }
    $freshStore=$connect($storeDsn,$runtime);$freshAi=$connect($aiDsn,$aiRuntime);
    $sourceQ=$freshStore->prepare(str_replace($s,$s,'SELECT HR.RECORD_ID,HR.SESSION_ID,HR.AI_USER_ID,HR.METRIC_CODE,HR.VALUE,HR.UNIT,HR.SEGMENT,HR.SEGMENT_KEY,HR.START_TIME,MS.EVENT_TYPE,MS.SOURCE_PLATFORM,MS.VENDOR,MS.DEVICE_TYPE,MS.DEVICE_MODEL,MS.BUSINESS_CONTEXT,MS.IS_VALID FROM '.$s.'health_record HR JOIN '.$s.'measurement_session MS ON MS.SESSION_ID=HR.SESSION_ID WHERE HR.AI_USER_ID=? AND MS.DEVICE_MODEL=? ORDER BY HR.START_TIME,HR.RECORD_ID'));
    $sourceQ->execute([$saved['user_id'],$marker]);if(hash('sha256',serialize($sourceQ->fetchAll(PDO::FETCH_ASSOC)))!==$sourceSha)throw new RuntimeException('Store fixture changed');
    $aiQ=$freshAi->prepare('SELECT USER_ID,STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME FROM '.$a.'user WHERE USER_ID=? AND NICK_NAME=?');$aiQ->execute([$saved['user_id'],$marker]);
    if(hash('sha256',serialize($aiQ->fetchAll(PDO::FETCH_ASSOC)))!==$aiSha)throw new RuntimeException('AI fixture changed');
    $results['fixture_unchanged']=true;
}catch(Throwable $error){if($store->inTransaction())$store->rollBack();if($ai->inTransaction())$ai->rollBack();
    $failure=get_class($error).':'.(string)$error->getCode().':'.hash('sha256',$error->getMessage());$results['failure_stage']=$stage;}
foreach($tempFiles as $file){if(is_file($file)&&strpos($file,$runtimeDir.'/CDXGHX')===0)unlink($file);}
if($httpUncertain||!is_array(json_decode((string)@file_get_contents($base.'/__poc_boundary'),true))
    ||trim((string)@file_get_contents($childrenPath))!==''){
    $saved['phase']='uncertain_http_retain_fixture';$save();
    echo json_encode(['status'=>'uncertain_http_retain_fixture','lane'=>$lane,'journal'=>$journal],JSON_UNESCAPED_SLASHES),PHP_EOL;exit(3);}

$stage='reference_scan';
$scan=static function(PDO $db,int $userId) use($dialect):array{
    if($dialect==='mysql'){$q=$db->query("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND COLUMN_NAME='ai_user_id' ORDER BY TABLE_NAME");}
    else{$q=$db->query("SELECT TABLE_NAME FROM ALL_TAB_COLUMNS WHERE OWNER='ZHCT_APP' AND COLUMN_NAME='AI_USER_ID' ORDER BY TABLE_NAME");}
    $out=[];foreach($q->fetchAll(PDO::FETCH_COLUMN) as $physical){if(!preg_match('/^YDY_[A-Z0-9_]+$/i',(string)$physical))continue;
        $c=$db->prepare('SELECT COUNT(*) FROM '.$physical.' WHERE AI_USER_ID=?');$c->execute([$userId]);$n=(int)$c->fetchColumn();if($n>0)$out[strtoupper((string)$physical)]=$n;}
    ksort($out);return $out;};
$refs=$scan($connect($storeDsn,$runtime),$saved['user_id']);$expected=['YDY_HEALTH_RECORD'=>2,'YDY_MEASUREMENT_SESSION'=>2];ksort($expected);
if($refs!==$expected){$saved['phase']='unexpected_reference_retain_fixture';$save();
    echo json_encode(['status'=>'unexpected_reference_retain_fixture','lane'=>$lane,'journal'=>$journal,
        'ref_sha'=>hash('sha256',serialize($refs))],JSON_UNESCAPED_SLASHES),PHP_EOL;exit(4);}

$stage='cleanup';$cleanStore=$connect($storeDsn,$runtime);$cleanAi=$connect($aiDsn,$aiRuntime);
$cleanStore->beginTransaction();$cleanAi->beginTransaction();
try{
    $q=$cleanStore->prepare('DELETE FROM '.$s.'health_record WHERE AI_USER_ID=? AND SESSION_ID IN (?,?) AND RECORD_ID IN (?,?)');
    $q->execute([$saved['user_id'],$saved['session_ids'][0],$saved['session_ids'][1],$saved['record_ids'][0],$saved['record_ids'][1]]);if($q->rowCount()!==2)throw new RuntimeException('Record cleanup mismatch');
    $q=$cleanStore->prepare('DELETE FROM '.$s.'measurement_session WHERE AI_USER_ID=? AND DEVICE_MODEL=? AND SESSION_ID IN (?,?)');
    $q->execute([$saved['user_id'],$marker,$saved['session_ids'][0],$saved['session_ids'][1]]);if($q->rowCount()!==2)throw new RuntimeException('Session cleanup mismatch');
    $q=$cleanAi->prepare('DELETE FROM '.$a.'user WHERE USER_ID=? AND NICK_NAME=? AND MOBILE=? AND STORE_ID=10001');
    $q->execute([$saved['user_id'],$marker,$marker]);if($q->rowCount()!==1)throw new RuntimeException('AI user cleanup mismatch');
    $cleanStore->commit();$cleanAi->commit();
}catch(Throwable $error){if($cleanStore->inTransaction())$cleanStore->rollBack();if($cleanAi->inTransaction())$cleanAi->rollBack();
    $saved['phase']='cleanup_failed_retain_fixture';$save();throw $error;}
$saved['phase']='fixture_deleted';$save();
if($countMarkers($connect($storeDsn,$runtime),$connect($aiDsn,$aiRuntime))!==0)throw new RuntimeException('Post-cleanup marker remains');
foreach(glob($runtimeDir.'/CDXGHX*')?:[] as $path)if(is_file($path))throw new RuntimeException('Post-cleanup XLS remains');
$results['cleanup']=['marker_rows'=>0,'temp_files'=>0,'references_before'=>$refs,'journal'=>$journal];
$status=$failure===''?'HTTP_PASS_CLEANED_PENDING_AUDIT':'HTTP_FAIL_CLEANED';if($failure!=='')$results['failure']=$failure;
echo json_encode(['status'=>$status,'lane'=>$lane,'results'=>$results],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES),PHP_EOL;
exit($failure===''?0:2);
