<?php
declare(strict_types=1);

// Read-only schema/charset/marker gate. No token, business HTTP, DML or external call.
$lane = (string)($argv[1] ?? '');
$marker = (string)($argv[2] ?? '');
if (!in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXPERSONAL[0-9A-F]{8}$/', $marker)) {
    fwrite(STDERR, "DENIED: exact B|C and marker required\n");
    exit(78);
}
$root = $lane === 'B' ? '/private/tmp/zhct-http-ai-macos.x2CCZX'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$pid = $lane === 'B' ? 13127 : 53772;
$port = $lane === 'B' ? 18101 : 18102;
if ($lane === 'B') {
    $cfg = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW)['DATABASE'] ?? null;
    $ps = (string)shell_exec('/bin/ps eww -p ' . $pid . ' -o command=');
    $owner = trim((string)shell_exec('/usr/sbin/lsof -nP -t -iTCP:' . $port . ' -sTCP:LISTEN 2>/dev/null'));
    if (!is_array($cfg) || strtolower((string)($cfg['TYPE'] ?? '')) !== 'mysql'
        || (string)($cfg['HOSTPORT'] ?? '') !== '3306' || $owner !== (string)$pid
        || strpos($ps, 'POC_DIALECT=mysql') === false
        || strpos($ps, 'POC_EXTERNAL_STUBS=1') === false) {
        throw new RuntimeException('B worker/config drift');
    }
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
              'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        $v = (string)($cfg[$from] ?? '');
        if ($v === '' || strpos($ps, 'POC_AI_' . $to . '=' . $v) === false) {
            throw new RuntimeException('B worker differs from code local config');
        }
    }
    $db = new PDO('mysql:host=' . $cfg['HOSTNAME'] . ';port=' . $cfg['HOSTPORT']
        . ';dbname=' . $cfg['DATABASE'] . ';charset=' . $cfg['CHARSET'],
        $cfg['USERNAME'], $cfg['PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    $connectionCharset = $db->query('SELECT @@character_set_connection')->fetchColumn();
    $databaseCharset = $db->query('SELECT @@character_set_database')->fetchColumn();
    $collation = $db->query('SELECT @@collation_connection')->fetchColumn();
} else {
    $env = [];
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $item) {
        $pair = explode('=', $item, 2);
        if (count($pair) === 2) $env[$pair[0]] = $pair[1];
    }
    if (($env['POC_TARGET_ROOT'] ?? '') !== $root
        || ($env['POC_BASE_URL'] ?? '') !== 'http://127.0.0.1:' . $port
        || ($env['POC_DIALECT'] ?? '') !== 'dm'
        || ($env['POC_EXTERNAL_STUBS'] ?? '') !== '1'
        || ($env['POC_AI_HOST'] ?? '') !== '127.0.0.1'
        || ($env['POC_AI_PORT'] ?? '') !== '5238'
        || ($env['POC_AI_SCHEMA'] ?? '') !== 'AIZHCT_APP'
        || ($env['POC_AI_USER'] ?? '') !== 'AIZHCT_APP'
        || !in_array('dm', PDO::getAvailableDrivers(), true)) {
        throw new RuntimeException('C worker/config drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP',
        $env['POC_AI_USER'], $env['POC_AI_PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'AIZHCT_APP') {
        throw new RuntimeException('C schema drift');
    }
    $connectionCharset = 'DM connection metadata not inferred';
    $databaseCharset = 'DM connection metadata not inferred';
    $collation = 'DM connection metadata not inferred';
}
$loader = require $root . '/vendor/autoload.php';
if (!is_object($loader) || !method_exists($loader, 'findFile')) {
    throw new RuntimeException('Composer loader absent');
}
$classes = [
    'app\\api\\controller\\User',
    'app\\api\\model\\User',
    'app\\api\\service\\user\\UserWeight',
    'app\\api\\service\\health\\MetricProfileSync',
    'app\\common\\Serve', 'app\\common\\UserApi',
];
$source = [];
foreach ($classes as $class) {
    $file = $loader->findFile($class);
    $real = is_string($file) ? realpath($file) : false;
    if (!is_string($real) || !is_file($real)) {
        throw new RuntimeException('Actual source unresolved');
    }
    $source[$class] = hash_file('sha256', $real);
}
$logical = ['user', 'staff', 'user_weight', 'user_score',
    'user_score_log', 'user_health_metric_record', 'user_bind'];
$tables = [];
foreach ($logical as $name) {
    $table = 'yoshop_' . ($lane === 'B' ? $name : strtoupper($name));
    if ($lane === 'B') {
        $s = $db->prepare('SELECT COLUMN_NAME,DATA_TYPE,COLUMN_TYPE,IS_NULLABLE,CHARACTER_SET_NAME,COLLATION_NAME,COLUMN_DEFAULT,EXTRA FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? ORDER BY ORDINAL_POSITION');
    } else {
        $s = $db->prepare('SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,DATA_PRECISION,DATA_SCALE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME=? ORDER BY COLUMN_ID');
    }
    $s->execute([$table]);
    $columns = $s->fetchAll(PDO::FETCH_ASSOC);
    if (count($columns) < 4) throw new RuntimeException('Target table schema absent');
    $safeFields = ['USER_ID', 'NICK_NAME', 'GENDER', 'AVATAR_ID',
        'PASSWORD_VERSION', 'ID', 'NAME', 'BIRTHDAY', 'HEIGHT', 'WEIGHT',
        'PHONE', 'DATE', 'SOURCE_KEY', 'METRIC_CODE', 'BIND_ID',
        'STORE_ID', 'IS_DELETE', 'CREATE_TIME', 'UPDATE_TIME', 'OPERATOR_NAME'];
    $selected = [];
    $requiredNoDefault = [];
    foreach ($columns as $column) {
        if (in_array(strtoupper((string)$column['column_name']), $safeFields, true)) {
            $selected[] = $column;
        }
        $nullable = strtoupper((string)($column['is_nullable'] ?? $column['nullable'] ?? ''));
        $default = $column['column_default'] ?? $column['data_default'] ?? null;
        $auto = stripos((string)($column['extra'] ?? ''), 'auto_increment') !== false;
        if (in_array($nullable, ['NO', 'N'], true) && $default === null && !$auto) {
            $requiredNoDefault[] = strtoupper((string)$column['column_name']);
        }
    }
    $stmt = $db->query('SELECT COUNT(*) FROM ' . $table . ' WHERE STORE_ID=10001');
    $tables[$name] = ['column_count' => count($columns),
        'selected_columns' => $selected,
        'required_no_default' => $requiredNoDefault,
        'store_rows' => (int)$stmt->fetchColumn()];
}
$userTable = 'yoshop_' . ($lane === 'B' ? 'user' : 'USER');
$staffTable = 'yoshop_' . ($lane === 'B' ? 'staff' : 'STAFF');
$st = $db->prepare('SELECT COUNT(*) FROM ' . $userTable
    . ' WHERE STORE_ID=10001 AND NICK_NAME LIKE ?');
$st->execute([$marker . '%']);
$userMarker = (int)$st->fetchColumn();
$st = $db->prepare('SELECT COUNT(*) FROM ' . $staffTable
    . ' WHERE STORE_ID=10001 AND NAME LIKE ?');
$st->execute([$marker . '%']);
$staffMarker = (int)$st->fetchColumn();
$refSql = $lane === 'B'
    ? "SELECT TABLE_NAME,COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE 'yoshop_%' AND UPPER(COLUMN_NAME)='USER_ID' ORDER BY TABLE_NAME"
    : "SELECT TABLE_NAME,COLUMN_NAME FROM USER_TAB_COLUMNS WHERE TABLE_NAME LIKE 'YOSHOP_%' AND COLUMN_NAME='USER_ID' ORDER BY TABLE_NAME";
$refs = $db->query($refSql)->fetchAll(PDO::FETCH_ASSOC);
if (count($refs) > 250) throw new RuntimeException('User reference metadata cap exceeded');
if ($lane === 'B') {
    $triggerCount = (int)$db->query("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN ('yoshop_user','yoshop_staff')")->fetchColumn();
    $fkCount = (int)$db->query("SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA=DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND (TABLE_NAME IN ('yoshop_user','yoshop_staff') OR REFERENCED_TABLE_NAME IN ('yoshop_user','yoshop_staff'))")->fetchColumn();
} else {
    $triggerCount = (int)$db->query("SELECT COUNT(*) FROM USER_TRIGGERS WHERE TABLE_NAME IN ('YOSHOP_USER','YOSHOP_STAFF')")->fetchColumn();
    $fkCount = (int)$db->query("SELECT COUNT(*) FROM USER_CONSTRAINTS WHERE CONSTRAINT_TYPE='R' AND (TABLE_NAME IN ('YOSHOP_USER','YOSHOP_STAFF') OR R_CONSTRAINT_NAME IN (SELECT CONSTRAINT_NAME FROM USER_CONSTRAINTS WHERE TABLE_NAME IN ('YOSHOP_USER','YOSHOP_STAFF'))) ")->fetchColumn();
}
$fourByte = "CDX4BYTE-\u{1F9EA}";
$roundtrip = ['equal' => false, 'error_class' => null,
    'expected_sha256' => hash('sha256', $fourByte), 'actual_sha256' => null];
try {
    $s = $db->prepare($lane === 'B' ? 'SELECT ?' : 'SELECT ? FROM DUAL');
    $s->execute([$fourByte]);
    $value = $s->fetchColumn();
    $roundtrip['equal'] = is_string($value) && $value === $fourByte;
    $roundtrip['actual_sha256'] = is_string($value) ? hash('sha256', $value) : null;
} catch (Throwable $error) {
    $roundtrip['error_class'] = get_class($error);
}
echo json_encode(['lane' => $lane, 'worker_port' => $port,
    'actual_source_sha256' => $source,
    'connection_charset' => $connectionCharset,
    'database_charset' => $databaseCharset,
    'connection_collation' => $collation,
    'tables' => $tables,
    'marker_counts' => ['user' => $userMarker, 'staff' => $staffMarker],
    'user_id_reference_columns' => $refs,
    'user_staff_triggers' => $triggerCount,
    'user_staff_foreign_keys' => $fkCount,
    'four_byte_select_parameter_roundtrip' => $roundtrip,
    'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), "\n";
