<?php
declare(strict_types=1);

$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$marker = (string)($argv[3] ?? '');
$auditId = (int)($argv[4] ?? 0);
if (!in_array($phase, ['--audit', '--run'], true) || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXWGT[A-F0-9]{12}$/D', $marker)
    || ($phase === '--run' && getenv('CDX_WEIGHT_RUN') !== 'OWNED_GOAL_WRITE_V1')) {
    fwrite(STDERR, "DENIED business_http=0 business_dml=0\n"); exit(78);
}
$root = $lane === 'B' ? '/private/tmp/cdx-ai-weight-private-20261009.a72p8V'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$port = $lane === 'B' ? 18211 : 18102;
$token = 'CDXWGTTK' . strtoupper(hash('sha256', $marker));
if (!is_file($root . '/public/index.php') || !is_file($root . '/vendor/autoload.php')
    || !is_file($root . '/external_stubs.php')) {
    throw new RuntimeException('isolated worker/stub root absent');
}
if ($lane === 'B') {
    $ini = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW);
    $ai = is_array($ini) ? ($ini['DATABASE'] ?? null) : null;
    $store = is_array($ini) ? ($ini['DATABASE_ZHCT'] ?? null) : null;
    if (!is_array($ai) || !is_array($store)
        || strtolower((string)($ai['TYPE'] ?? '')) !== 'mysql'
        || (string)($ai['HOSTPORT'] ?? '') !== '3306'
        || hash('sha256', (string)($ai['HOSTNAME'] ?? ''))
            !== '47e8be48b0cfd56dcdb9d9d426e6637b245f340b4efad4cd2f084ec5851ecc8e'
        || hash('sha256', (string)($ai['DATABASE'] ?? ''))
            !== 'cf7756b3bb52cf72f625689134015b877fdab3ad8eb3bb2ddd962bb35f6efd66') {
        throw new RuntimeException('B code-local MySQL identity drift');
    }
    $db = new PDO('mysql:host=' . $ai['HOSTNAME'] . ';port=3306;dbname=' . $ai['DATABASE']
        . ';charset=utf8mb4', $ai['USERNAME'], $ai['PASSWORD']);
    $storeDb = new PDO('mysql:host=' . $store['HOSTNAME'] . ';port=3306;dbname='
        . $store['DATABASE'] . ';charset=utf8mb4', $store['USERNAME'], $store['PASSWORD']);
    if (strpos((string)$db->query('SELECT VERSION()')->fetchColumn(), '5.6.') !== 0
        || strcasecmp((string)$db->query('SELECT DATABASE()')->fetchColumn(), $ai['DATABASE']) !== 0) {
        throw new RuntimeException('B is not configured development MySQL 5.6');
    }
    foreach (['POC_DIALECT' => 'mysql', 'POC_AI_HOST' => $ai['HOSTNAME'],
        'POC_AI_PORT' => $ai['HOSTPORT'], 'POC_AI_SCHEMA' => $ai['DATABASE'],
        'POC_AI_USER' => $ai['USERNAME'], 'POC_AI_PASSWORD' => $ai['PASSWORD'],
        'POC_HOST' => $store['HOSTNAME'], 'POC_PORT' => $store['HOSTPORT'],
        'POC_SCHEMA' => $store['DATABASE'], 'POC_USER' => $store['USERNAME'],
        'POC_PASSWORD' => $store['PASSWORD']] as $key => $value) {
        putenv($key . '=' . $value);
    }
} else {
    if (getenv('POC_DIALECT') !== 'dm' || getenv('POC_AI_HOST') !== '127.0.0.1'
        || getenv('POC_AI_PORT') !== '5238' || getenv('POC_AI_SCHEMA') !== 'AIZHCT_APP'
        || getenv('POC_HOST') !== '127.0.0.1' || getenv('POC_PORT') !== '5237'
        || getenv('POC_SCHEMA') !== 'ZHCT_APP' || getenv('POC_EXTERNAL_STUBS') !== '1') {
        throw new RuntimeException('C isolated dual database identity drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP',
        getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'));
    $storeDb = new PDO('dm:host=127.0.0.1;port=5237;schema=ZHCT_APP',
        getenv('POC_USER'), getenv('POC_PASSWORD'));
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'AIZHCT_APP'
        || $storeDb->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'ZHCT_APP') {
        throw new RuntimeException('C DM effective schemas drift');
    }
}
foreach ([$db, $storeDb] as $connection) {
    $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $connection->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
}
$tables = ['user', 'staff', 'user_weight_status', 'user_weight', 'user_score',
    'user_score_log', 'user_bind'];
$table = static function (string $name) use ($tables, $lane): string {
    if (!in_array($name, $tables, true)) throw new RuntimeException('unlisted AI table');
    return 'yoshop_' . ($lane === 'B' ? $name : strtoupper($name));
};
$one = static function (PDO $conn, string $sql, array $args = []) {
    $q = $conn->prepare($sql); $q->execute($args); return $q->fetchColumn();
};
$row = static function (PDO $conn, string $sql, array $args = []): ?array {
    $q = $conn->prepare($sql); $q->execute($args);
    $v = $q->fetch(PDO::FETCH_ASSOC);
    return is_array($v) ? array_change_key_case($v, CASE_LOWER) : null;
};
$countOwn = static function (int $userId, string $name) use ($one, $db, $table): int {
    if ($userId <= 0) return 0;
    $idCol = $name === 'user' ? 'user_id' : 'user_id';
    return (int)$one($db, 'SELECT COUNT(*) FROM ' . $table($name)
        . ' WHERE ' . $idCol . '=? AND store_id=10001', [$userId]);
};
$storeCount = static function (int $userId) use ($one, $storeDb, $lane): int {
    if ($userId <= 0) return 0;
    $name = $lane === 'B' ? 'ydy_user' : 'YDY_USER';
    return (int)$one($storeDb, 'SELECT COUNT(*) FROM ' . $name
        . ' WHERE ai_user_id=? AND is_delete=0', [$userId]);
};
$triggerNames = array_map(static function (string $v) use ($table): string {
    return $table($v);
}, $tables);
$holders = implode(',', array_fill(0, count($triggerNames), '?'));
$triggerCount = $lane === 'B'
    ? (int)$one($db, 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS'
        . ' WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (' . $holders . ')', $triggerNames)
    : (int)$one($db, 'SELECT COUNT(*) FROM USER_TRIGGERS'
        . ' WHERE TABLE_NAME IN (' . $holders . ')', $triggerNames);
$priorMarker = (int)$one($db, 'SELECT COUNT(*) FROM ' . $table('user')
    . ' WHERE nick_name=? AND store_id=10001', [$marker]);
if ($phase === '--audit') {
    $counts = [];
    foreach ($tables as $name) $counts[$name] = $countOwn($auditId, $name);
    echo json_encode(['lane' => $lane, 'phase' => 'audit', 'marker_rows' => $priorMarker,
        'target_table_triggers' => $triggerCount, 'owned_ai_counts' => $counts,
        'store_active_mapping_count' => $storeCount($auditId),
        'business_http' => 0, 'business_dml' => 0]), PHP_EOL;
    exit($priorMarker === 0 && array_sum($counts) === 0 && $storeCount($auditId) === 0 ? 0 : 2);
}
if ($priorMarker !== 0 || $triggerCount !== 0) {
    throw new RuntimeException('fixture marker collision or table trigger');
}
$cmdline = $lane === 'B' ? (string)shell_exec('/bin/ps -p 24137 -o command=')
    : str_replace("\0", ' ', (string)file_get_contents('/proc/53772/cmdline'));
if (($lane === 'B' && strpos($cmdline, '-S 127.0.0.1:18211 -t ' . $root . '/public') === false)
    || ($lane === 'C' && strpos($cmdline, '-S 127.0.0.1:18102 -t ' . $root . '/public') === false)) {
    throw new RuntimeException('isolated HTTP worker PID/root drift');
}
$boundary = @file_get_contents('http://127.0.0.1:' . $port . '/__poc_boundary');
$boundaryJson = is_string($boundary) ? json_decode($boundary, true) : null;
if (!is_array($boundaryJson) || ($boundaryJson['external_stubs'] ?? false) !== true
    || ($lane === 'B' && ($boundaryJson['private_cache'] ?? '') !== $root . '/runtime/cache')) {
    throw new RuntimeException('actual external stub/private cache boundary failed');
}
foreach ([$root . '/app/api/service/user/UserWeight.php',
    $root . '/app/api/model/User.php', $root . '/external_stubs.php'] as $source) {
    if (!is_file($source)) throw new RuntimeException('worker source missing');
}
if (hash_file('sha256', $root . '/app/api/service/user/UserWeight.php')
    !== '532cc5b894d09f838bb583997e51a8fb0747e7a47c04053a3ec8746ae142a797') {
    throw new RuntimeException('UserWeight source version drift');
}

define('TP_ENV', $lane === 'B' ? 'local' : 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require_once $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName(TP_ENV)->initialize();
if (strtolower((string)\think\facade\Config::get('cache.default')) !== 'file'
    || realpath((string)\think\facade\Config::get('cache.stores.file.path'))
        !== realpath($root . '/runtime/cache') || \think\facade\Cache::has($token)) {
    throw new RuntimeException('private File token preflight failed');
}
$userId = 0;
$staffId = 0;
$status = null;
$result = [];
$caught = null;
$do = static function (string $sql, array $args = []) use ($db): int {
    $q = $db->prepare($sql); $q->execute($args); return $q->rowCount();
};
$ownCounts = static function (int $id) use ($countOwn, $tables): array {
    $counts = [];
    foreach ($tables as $name) $counts[$name] = $countOwn($id, $name);
    return $counts;
};
$request = static function (string $method, string $path, array $form = []) use ($port, $token): array {
    if (!in_array($path, ['/api/User/assets', '/api/user.UserWeight/weightManage',
        '/api/user.UserWeight/weightManageInfo'], true)) throw new RuntimeException('unlisted HTTP route');
    $headers = "Access-Token: $token\r\nplatform: POC\r\nversion: 2.0.51\r\nenv-version: develop\r\n";
    $opts = ['method' => $method, 'header' => $headers,
        'ignore_errors' => true, 'timeout' => 20, 'follow_location' => 0];
    if ($method === 'POST') {
        $opts['header'] .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $opts['content'] = http_build_query(['form' => $form]);
    }
    $context = stream_context_create(['http' => $opts]);
    $handle = @fopen('http://127.0.0.1:' . $port . $path . '?storeId=10001', 'rb', false, $context);
    if (!is_resource($handle)) throw new RuntimeException('isolated HTTP unavailable');
    $raw = stream_get_contents($handle, 1048577); fclose($handle);
    if (!is_string($raw) || strlen($raw) > 1048576) throw new RuntimeException('HTTP response cap');
    $body = json_decode($raw, true);
    if (!is_array($body)) throw new RuntimeException('HTTP non-JSON');
    $httpLine = (string)($http_response_header[0] ?? '');
    if (!preg_match('/^HTTP\/\S+\s+(\d{3})\b/', $httpLine, $match)
        || (int)$match[1] !== 200) throw new RuntimeException('HTTP status not 200');
    return $body;
};
$typeGraph = static function ($value) use (&$typeGraph) {
    if (!is_array($value)) return gettype($value);
    if (!$value) return [];
    if (array_keys($value) === range(0, count($value) - 1)) return array_map($typeGraph, $value);
    ksort($value); return array_map($typeGraph, $value);
};
try {
    $now = time();
    $do('INSERT INTO ' . $table('user')
        . " (store_id,mobile,nick_name,balance,gift_balance,is_delete,create_time) VALUES (10001,'',?,'0.00','0.00',0,?)",
        [$marker, $now]);
    $owned = $row($db, 'SELECT user_id,nick_name FROM ' . $table('user')
        . ' WHERE nick_name=? AND store_id=10001 AND is_delete=0', [$marker]);
    if (!$owned) throw new RuntimeException('owned AI user insert not visible');
    $userId = (int)$owned['user_id'];
    $do('INSERT INTO ' . $table('staff')
        . " (user_id,name,store_id,height,weight,gender,birthday,phone,is_delete) VALUES (?,?,10001,170.00,60.00,1,'1990-01-01','',0)",
        [$userId, $marker]);
    $staff = $row($db, 'SELECT id,weight,height FROM ' . $table('staff')
        . ' WHERE user_id=? AND name=? AND store_id=10001 AND is_delete=0', [$userId, $marker]);
    if (!$staff || (float)$staff['weight'] !== 60.0) throw new RuntimeException('owned staff insert not visible');
    $staffId = (int)$staff['id'];
    if ($ownCounts($userId) !== ['user'=>1,'staff'=>1,'user_weight_status'=>0,
        'user_weight'=>0,'user_score'=>0,'user_score_log'=>0,'user_bind'=>0]
        || $storeCount($userId) !== 0) throw new RuntimeException('owned fixture baseline not isolated');
    $ormUser = \app\api\model\User::detail($userId);
    if (!$ormUser || (int)$ormUser['user_id'] !== $userId
        || !\think\facade\Cache::set($token, ['user' => ['user_id' => $userId],
            'password_version' => 0, 'login_type' => 'thirdparty'], 300)) {
        throw new RuntimeException('owned user/token setup failed');
    }
    $pre = $request('GET', '/api/User/assets');
    if ((int)($pre['status'] ?? 0) !== 200) throw new RuntimeException('own token asset preflight failed');
    $today = date('Y-m-d');
    $status = $request('POST', '/api/user.UserWeight/weightManage', [
        'initial_weight' => '60.25', 'start_date' => $today,
        'target_weight' => '58.25', 'adjust_speed' => '2',
    ]);
    if ((int)($status['status'] ?? 0) !== 200) {
        throw new RuntimeException('weightManage business status=' . (string)($status['status'] ?? 'missing')
            . ' message=' . substr((string)($status['message'] ?? ''), 0, 100));
    }
    $data = $status['data'] ?? null;
    if (!is_array($data) || (int)($data['user_id'] ?? 0) !== $userId
        || (string)($data['initial_weight'] ?? '') !== '60.25'
        || (string)($data['target_weight'] ?? '') !== '58.25'
        || (int)($data['status'] ?? 0) !== 3 || (int)($data['adjust_speed'] ?? 0) !== 2) {
        throw new RuntimeException('weightManage response differs from own input');
    }
    $goal = $row($db, 'SELECT id,user_id,start_date,initial_weight,target_weight,status,adjust_speed,delete_flag'
        . ' FROM ' . $table('user_weight_status') . ' WHERE user_id=? AND store_id=10001', [$userId]);
    $weight = $row($db, 'SELECT id,user_id,date,height,weight,bmi FROM ' . $table('user_weight')
        . ' WHERE user_id=? AND store_id=10001', [$userId]);
    $staffAfter = $row($db, 'SELECT id,weight,height FROM ' . $table('staff')
        . ' WHERE id=? AND user_id=? AND name=?', [$staffId, $userId, $marker]);
    $score = $row($db, 'SELECT id,score,type FROM ' . $table('user_score')
        . ' WHERE user_id=? AND store_id=10001', [$userId]);
    $log = $row($db, 'SELECT id,score,type,date FROM ' . $table('user_score_log')
        . ' WHERE user_id=? AND store_id=10001', [$userId]);
    $bind = $row($db, 'SELECT bind_id,user_id,zhct_user_id FROM ' . $table('user_bind')
        . ' WHERE user_id=? AND store_id=10001', [$userId]);
    if ($ownCounts($userId) !== ['user'=>1,'staff'=>1,'user_weight_status'=>1,
        'user_weight'=>1,'user_score'=>1,'user_score_log'=>1,'user_bind'=>1]
        || !$goal || !$weight || !$staffAfter || !$score || !$log || !$bind
        || (int)$goal['start_date'] !== strtotime($today)
        || (string)$goal['initial_weight'] !== '60.25'
        || (string)$goal['target_weight'] !== '58.25'
        || (int)$goal['status'] !== 3 || (int)$goal['adjust_speed'] !== 2
        || (int)$goal['delete_flag'] !== 0
        || (int)$weight['date'] !== strtotime($today)
        || (string)$weight['weight'] !== '60.25'
        || (float)$weight['height'] !== 170.0
        || (float)$staffAfter['weight'] !== 60.25
        || (int)$score['score'] !== 1 || (int)$score['type'] !== 1
        || (int)$log['score'] !== 1 || (int)$log['type'] !== 1
        || (int)$bind['zhct_user_id'] !== 0 || $storeCount($userId) !== 0) {
        throw new RuntimeException('weightManage seven-table source readback mismatch');
    }
    $info = $request('GET', '/api/user.UserWeight/weightManageInfo');
    if ((int)($info['status'] ?? 0) !== 200
        || (string)($info['data']['initial_weight'] ?? '') !== '60.25') {
        throw new RuntimeException('weightManageInfo cannot read own new goal');
    }
    $result = ['lane' => $lane, 'marker' => $marker, 'owned_user_id' => $userId,
        'http_business_ok' => 3, 'goal_weight_staff_score_log_bind_readback' => true,
        'store_active_mapping_after_stub' => $storeCount($userId),
        'write_response_type_sha256' => hash('sha256', json_encode($typeGraph($status))),
        'write_data_type_sha256' => hash('sha256', json_encode($typeGraph($data))),
        'info_response_type_sha256' => hash('sha256', json_encode($typeGraph($info))),
        'important_data_types' => array_map('gettype', array_intersect_key($data,
            array_flip(['initial_weight','target_weight','status','adjust_speed','start_date','end_date']))),
        'target_table_triggers' => $triggerCount];
} catch (Throwable $e) {
    $caught = $e;
} finally {
    if ($db->inTransaction()) $db->rollBack();
    if ($userId > 0) {
        foreach (['user_bind','user_score_log','user_score','user_weight',
            'user_weight_status','staff'] as $name) {
            $sql = 'DELETE FROM ' . $table($name) . ' WHERE user_id=? AND store_id=10001';
            if ($name === 'staff') $sql .= ' AND name=?';
            $do($sql, $name === 'staff' ? [$userId, $marker] : [$userId]);
        }
        $do('DELETE FROM ' . $table('user')
            . ' WHERE user_id=? AND nick_name=? AND store_id=10001', [$userId, $marker]);
    }
    \think\facade\Cache::delete($token);
}
$after = $ownCounts($userId);
if ($priorMarker !== 0 || array_sum($after) !== 0 || $storeCount($userId) !== 0
    || \think\facade\Cache::has($token)) {
    throw new RuntimeException('owned seven-table/token cleanup not zero');
}
if ($caught !== null) {
    fwrite(STDERR, 'TEST_FAILED own_id=' . $userId . ' reason=' . $caught->getMessage() . "\n");
    exit(2);
}
echo json_encode($result + ['independent_audit_arg' => $userId,
    'owned_ai_rows_cleanup_zero' => true, 'owned_token_cleanup_zero' => true,
    'real_queue_or_store_http' => 0], JSON_UNESCAPED_SLASHES), PHP_EOL;
