<?php
declare(strict_types=1);

// Isolated Store mobile staff read probe. Never prints names, mobiles, image data, or tokens.
$lane = (string)($argv[1] ?? '');
$phase = (string)($argv[2] ?? '');
if (!in_array($lane, ['B', 'C'], true) || !in_array($phase, ['preflight', 'run'], true)
    || getenv('CDX_MOBILE_STAFF_READ') !== 'SOURCE_READ_ONLY_V1') {
    fwrite(STDERR, "DENIED: lane/phase/gate\n");
    exit(78);
}

if ($lane === 'B') {
    $root = '/tmp/zhct-http-store-mysql.dQTTBP';
    $config = include $root . '/application/config/local/database.php';
    if (!is_array($config) || ($config['database'] ?? '') !== 'zhct'
        || hash('sha256', (string)($config['hostname'] ?? ''))
            !== '47e8be48b0cfd56dcdb9d9d426e6637b245f340b4efad4cd2f084ec5851ecc8e') {
        throw new RuntimeException('B code-local DB identity drift');
    }
    $db = new PDO('mysql:host=' . $config['hostname'] . ';port=3306;dbname=zhct;charset=utf8',
        $config['username'], $config['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    $port = 18081;
} else {
    $root = '/tmp/zhct-dm-poc-20260928.fcpiiN/store';
    if (getenv('POC_DIALECT') !== 'dm' || getenv('POC_HOST') !== '127.0.0.1'
        || getenv('POC_PORT') !== '5237' || getenv('POC_SCHEMA') !== 'ZHCT_APP'
        || getenv('POC_USER') !== 'ZHCT_APP') {
        throw new RuntimeException('C isolated Store DB identity drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5237;schema=ZHCT_APP',
        getenv('POC_USER'), getenv('POC_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() !== 'ZHCT_APP') {
        throw new RuntimeException('C schema drift');
    }
    $port = 18080;
}
$limit = $lane === 'B' ? ' LIMIT 80' : ' FETCH FIRST 80 ROWS ONLY';
$rows = $db->query('SELECT s.id,s.uuid,s.name,s.nickname,s.mobile,s.height,s.weight,s.sex,s.birthday,'
    . 's.sport_uuid,u.id AS user_id,u.uuid AS user_uuid,u.third_type'
    . ' FROM ydy_staff s JOIN ydy_user u ON u.staff_uuid=s.uuid'
    . ' JOIN ydy_sport sp ON sp.uuid=s.sport_uuid'
    . ' JOIN ydy_staff_department sd ON sd.staff_uuid=s.uuid'
    . ' JOIN ydy_department d ON d.uuid=sd.department_uuid AND d.is_show=1'
    . ' LEFT JOIN ydy_hardware_img h ON h.staff_uuid=s.uuid AND h.is_del=0'
    . " WHERE s.is_show=1 AND s.mobile IS NOT NULL AND s.mobile<>''"
    . ' AND s.height>0 AND s.weight>0 AND h.id IS NULL'
    . ' ORDER BY s.id DESC' . $limit)->fetchAll(PDO::FETCH_ASSOC);
$rows = array_map(static function (array $row): array {
    return array_change_key_case($row, CASE_LOWER);
}, $rows);
$count = static function (string $sql, array $args = []) use ($db): int {
    $stmt = $db->prepare($sql);
    $stmt->execute($args);
    return (int)$stmt->fetchColumn();
};
$candidate = null;
$infoCandidate = null;
$todayEnd = date('Y-m-d 23:59:59');
foreach ($rows as $row) {
    if ($count('SELECT COUNT(*) FROM ydy_staff WHERE mobile=? AND is_show=1', [(string)$row['mobile']]) !== 1) continue;
    if ($candidate === null) $candidate = $row;
    if ($infoCandidate === null && $count('SELECT COUNT(*) FROM ydy_user_code'
        . ' WHERE user_id=? AND end_time=?', [(int)$row['user_id'], $todayEnd]) > 0) $infoCandidate = $row;
    if ($candidate !== null && $infoCandidate !== null) break;
}
$faceCount = $count('SELECT COUNT(*) FROM ydy_hardware_img'
    . ' WHERE is_del=0 AND head_img IS NOT NULL AND LENGTH(head_img)>0');
$safe = [
    'lane' => $lane,
    'source_staff_scanned' => count($rows),
    'base_candidate' => $candidate !== null,
    'info_existing_today_code_candidate' => $infoCandidate !== null,
    'positive_face_source_rows' => $faceCount,
];
if ($phase === 'preflight') {
    echo json_encode($safe, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(0);
}

$http = static function (string $action, array $form, string $token = '') use ($port): array {
    $allow = ['getBaseInfo', 'getbaseinfo', 'getBaseInfoByMobile', 'getFaceImg', 'info'];
    if (!in_array($action, $allow, true)) throw new RuntimeException('HTTP action boundary');
    $body = http_build_query($form, '', '&', PHP_QUERY_RFC3986);
    $headers = "Content-Type: application/x-www-form-urlencoded\r\n";
    if ($token !== '') $headers .= 'token: ' . $token . "\r\n";
    $ctx = stream_context_create(['http' => [
        'method' => 'POST', 'header' => $headers, 'content' => $body,
        'ignore_errors' => true, 'timeout' => 40, 'follow_location' => 0,
    ]]);
    $stream = @fopen('http://127.0.0.1:' . $port . '/m/staff/' . $action, 'rb', false, $ctx);
    if (!is_resource($stream)) throw new RuntimeException('isolated Staff HTTP unavailable');
    $raw = stream_get_contents($stream, 8388609);
    $meta = stream_get_meta_data($stream);
    $httpHeaders = $http_response_header ?? [];
    fclose($stream);
    preg_match('/HTTP\/\S+\s+(\d+)/', (string)($httpHeaders[0] ?? ''), $match);
    $bodyJson = is_string($raw) && strlen($raw) <= 8388608 ? json_decode($raw, true) : null;
    if ((int)($match[1] ?? 0) !== 200 || !empty($meta['timed_out']) || !is_array($bodyJson)
        || json_last_error() !== JSON_ERROR_NONE) throw new RuntimeException('Staff HTTP unknown/non-JSON');
    return $bodyJson;
};
$types = static function (array $row): array {
    $result = [];
    foreach ($row as $key => $value) $result[$key] = gettype($value);
    ksort($result);
    return $result;
};
require_once $root . '/application/base/Security.php';
$makeToken = static function (array $row): string {
    return \app\base\Security::encrypt(implode("\t", [
        (string)$row['user_id'], (string)$row['user_uuid'], 'CDX_READ', '0', '',
        (string)$row['uuid'], 'CDX_READ', '',
    ]));
};
$results = [];
$sourceShaBefore = null;
$targetCodeCountBefore = null;
$targetImageCountBefore = null;
if ($candidate !== null) {
    $staffSource = $db->prepare('SELECT uuid,name,nickname,mobile,height,weight,sex,birthday,sport_uuid'
        . ' FROM ydy_staff WHERE uuid=?');
    $staffSource->execute([(string)$candidate['uuid']]);
    $sourceShaBefore = hash('sha256', serialize($staffSource->fetch(PDO::FETCH_ASSOC)));
    $targetCodeCountBefore = $count('SELECT COUNT(*) FROM ydy_user_code WHERE user_id=?',
        [(int)$candidate['user_id']]);
    $targetImageCountBefore = $count('SELECT COUNT(*) FROM ydy_hardware_img WHERE staff_uuid=?',
        [(string)$candidate['uuid']]);
}
if ($candidate !== null) {
    $token = $makeToken($candidate);
    $byUuid = $http('getBaseInfo', ['staff_uuid' => $candidate['uuid']], $token);
    $byLowercase = $http('getbaseinfo', ['staff_uuid' => $candidate['uuid']], $token);
    $byMobile = $http('getBaseInfoByMobile', ['mobile' => $candidate['mobile']]);
    foreach (['getBaseInfo' => $byUuid, 'getbaseinfo' => $byLowercase,
        'getBaseInfoByMobile' => $byMobile] as $action => $body) {
        $data = $body['data'] ?? null;
        $checks = [
            'code' => (int)($body['code'] ?? -1) === 0,
            'data_array' => is_array($data),
            'uuid' => (string)($data['uuid'] ?? '') === (string)$candidate['uuid'],
            'name' => (string)($data['name'] ?? '') === (string)(empty($candidate['nickname']) ? $candidate['name'] : $candidate['nickname']),
            'height' => is_string($data['height'] ?? null)
                && (string)$data['height'] === number_format((float)$candidate['height'], 2, '.', ''),
            'weight_numeric' => is_numeric($data['weight'] ?? null)
                && abs((float)$data['weight'] - (float)$candidate['weight']) < 0.00001,
        ];
        if (in_array(false, $checks, true)) {
            throw new RuntimeException($action . ' response/source mismatch checks=' . json_encode($checks)
                . ' source_weight_type=' . gettype($candidate['weight'])
                . ' http_weight_type=' . gettype($data['weight'] ?? null)
                . ' source_weight_text_length=' . strlen((string)$candidate['weight'])
                . ' http_weight_text_length=' . strlen((string)($data['weight'] ?? '')));
        }
        $results[$action] = [
            'http' => 200, 'code' => 0, 'source_match' => true,
            'height_type' => gettype($data['height'] ?? null),
            'weight_type' => gettype($data['weight'] ?? null),
            'data_type_sha256' => hash('sha256', json_encode($types($data))),
        ];
    }
    if ($types($byUuid['data']) !== $types($byMobile['data'])) {
        throw new RuntimeException('base staff routes type mismatch in same lane');
    }
    if ($byLowercase !== $byUuid) {
        throw new RuntimeException('lowercase base info alias differs from camelCase in same lane');
    }
}
if ($infoCandidate !== null) {
    $codeCountBefore = $count('SELECT COUNT(*) FROM ydy_user_code WHERE user_id=? AND end_time=?',
        [(int)$infoCandidate['user_id'], $todayEnd]);
    if ($codeCountBefore <= 0) throw new RuntimeException('info existing-code guard changed');
    $body = $http('info', ['uuid' => $infoCandidate['uuid']], $makeToken($infoCandidate));
    $codeCountAfter = $count('SELECT COUNT(*) FROM ydy_user_code WHERE user_id=? AND end_time=?',
        [(int)$infoCandidate['user_id'], $todayEnd]);
    if ($codeCountBefore !== $codeCountAfter) throw new RuntimeException('info created an unexpected code');
    $data = $body['data'][0] ?? null;
    if ((int)($body['code'] ?? -1) !== 0 || !is_array($data)
        || (string)($data['uuid'] ?? '') !== (string)$infoCandidate['uuid']) {
        throw new RuntimeException('info response/source mismatch');
    }
    $results['info'] = [
        'http' => 200, 'code' => 0, 'source_match' => true,
        'data_type_sha256' => hash('sha256', json_encode($types($data))),
        'user_code_row_delta' => 0,
    ];
}
if ($faceCount > 0 && ($candidate !== null || $infoCandidate !== null)) {
    $sql = 'SELECT id,staff_uuid,head_img FROM ydy_hardware_img'
        . ' WHERE is_del=0 AND head_img IS NOT NULL AND LENGTH(head_img)>0'
        . ' ORDER BY id DESC' . ($lane === 'B' ? ' LIMIT 1' : ' FETCH FIRST 1 ROWS ONLY');
    $faceRow = array_change_key_case($db->query($sql)->fetch(PDO::FETCH_ASSOC), CASE_LOWER);
    if ($count('SELECT COUNT(*) FROM ydy_hardware_img WHERE staff_uuid=? AND is_del=0 AND id>?',
        [(string)$faceRow['staff_uuid'], (int)$faceRow['id']]) > 0) {
        throw new RuntimeException('positive face is not latest staff image');
    }
    $source = $faceRow['head_img'];
    if (is_resource($source)) $source = stream_get_contents($source);
    $source = (string)$source;
    if ($source === '') throw new RuntimeException('positive face source empty');
    $body = $http('getFaceImg', ['staff_uuid' => $faceRow['staff_uuid']], $makeToken($candidate ?? $infoCandidate));
    if ((int)($body['code'] ?? -1) !== 0 || !is_string($body['data'] ?? null)
        || hash('sha256', $body['data']) !== hash('sha256', $source)) {
        throw new RuntimeException('getFaceImg response/source mismatch');
    }
    $results['getFaceImg'] = [
        'http' => 200, 'code' => 0, 'source_match' => true,
        'image_len' => strlen($source), 'data_type' => gettype($body['data']),
    ];
} elseif ($candidate !== null) {
    $body = $http('getFaceImg', ['staff_uuid' => $candidate['uuid']], $makeToken($candidate));
    if ((int)($body['code'] ?? -1) !== 0 || ($body['data'] ?? null) !== '') {
        throw new RuntimeException('getFaceImg no-image mismatch');
    }
    $results['getFaceImg'] = ['http' => 200, 'code' => 0,
        'source_empty_match' => true, 'data_type' => 'string'];
}
$sourceRowsUnchanged = null;
$targetCodeCountUnchanged = null;
$targetImageCountUnchanged = null;
if ($candidate !== null) {
    $staffSource->execute([(string)$candidate['uuid']]);
    $sourceRowsUnchanged = hash('sha256', serialize($staffSource->fetch(PDO::FETCH_ASSOC))) === $sourceShaBefore;
    $targetCodeCountUnchanged = $count('SELECT COUNT(*) FROM ydy_user_code WHERE user_id=?',
        [(int)$candidate['user_id']]) === $targetCodeCountBefore;
    $targetImageCountUnchanged = $count('SELECT COUNT(*) FROM ydy_hardware_img WHERE staff_uuid=?',
        [(string)$candidate['uuid']]) === $targetImageCountBefore;
    if (!$sourceRowsUnchanged || !$targetCodeCountUnchanged || !$targetImageCountUnchanged) {
        throw new RuntimeException('target source mutation or concurrent drift');
    }
}
$safe['results'] = $results;
$safe['target_source_rows_unchanged'] = $sourceRowsUnchanged;
$safe['target_user_code_count_unchanged'] = $targetCodeCountUnchanged;
$safe['target_image_count_unchanged'] = $targetImageCountUnchanged;
$safe['business_dml'] = 0;
$safe['image_download'] = 0;
echo json_encode($safe, JSON_UNESCAPED_SLASHES), PHP_EOL;
