<?php
declare(strict_types=1);

// One Store staff/sport read per lane. Exact source, one disabled actor, no real external effects.
$lane = (string)getenv('CDX_SPORT_LANE');
$mode = (string)($argv[1] ?? '');
$targets = [
    'A' => ['/tmp/zhct-http-store-a.l5omH8', 'http://127.0.0.1:18097', 21023,
        'mysql', '1f4570973a9d92aa7bd0f5908b66916fcc06f5948c2520e964b0de09ef1e903d'],
    'B' => ['/tmp/zhct-http-store-mysql.dQTTBP', 'http://127.0.0.1:18081', 10001,
        'mysql', '0926a7240a54afbe8c0435c26810c2f3745fe3645d2e11c1dcb607f76a4eee01'],
    'C' => ['/tmp/zhct-dm-poc-20260928.fcpiiN/store', 'http://127.0.0.1:18080', 112175,
        'dm', '0926a7240a54afbe8c0435c26810c2f3745fe3645d2e11c1dcb607f76a4eee01'],
];
if (!isset($targets[$lane]) || !in_array($mode, ['--preflight', '--execute'], true)
    || ($mode === '--execute'
        && ((string)getenv('CDX_SPORT_APPROVAL') !== 'ROOT_APPROVED_SPORT_ONCE'
            || (string)getenv('CDX_SPORT_SHA') !== hash_file('sha256', __FILE__)))) {
    echo json_encode(['status' => 'default_deny', 'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(78);
}
[$root, $base, $pid, $dialect, $sourceSha] = $targets[$lane];
if ($lane === 'C' && getenv('CDX_SPORT_CHILD') !== '1') {
    $processCmd = (string)file_get_contents('/proc/' . $pid . '/cmdline');
    if (strpos($processCmd, $root . '/public') === false
        || strpos($processCmd, '127.0.0.1:18080') === false) {
        throw new RuntimeException('C HTTP worker changed');
    }
    $environment = [];
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $item) {
        $at = strpos($item, '=');
        if ($at !== false) $environment[substr($item, 0, $at)] = substr($item, $at + 1);
    }
    $svc = (string)($environment['DM_SVC_PATH'] ?? '');
    if (substr(hash('sha256', $svc), 0, 12) !== '5a92d26aec07'
        || !is_file($svc . '/dm_svc.conf')
        || strpos((string)file_get_contents($svc . '/dm_svc.conf'), 'CHAR_CODE=(PG_UTF8)') === false) {
        throw new RuntimeException('C client charset profile changed');
    }
    $environment['CDX_SPORT_CHILD'] = '1';
    $environment['CDX_SPORT_LANE'] = 'C';
    $environment['POC_APP_ROOT'] = $root;
    $environment['POC_BASE_URL'] = $base;
    $environment['POC_DIALECT'] = 'dm';
    if ($mode === '--execute') {
        $environment['CDX_SPORT_APPROVAL'] = 'ROOT_APPROVED_SPORT_ONCE';
        $environment['CDX_SPORT_SHA'] = hash_file('sha256', __FILE__);
    }
    $cmd = escapeshellarg('/www/server/php/73/bin/php')
        . ' -d display_errors=0 -d log_errors=0'
        . ' -d extension=/home/dmdba/dmdbms/drivers/php_pdo/php73_pdo_dm.so '
        . escapeshellarg(__FILE__) . ' ' . escapeshellarg($mode);
    $process = proc_open($cmd, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']],
        $pipes, null, $environment);
    if (!is_resource($process)) throw new RuntimeException('C child unavailable');
    $stdout = stream_get_contents($pipes[1]);
    $stderr = stream_get_contents($pipes[2]);
    fclose($pipes[1]);
    fclose($pipes[2]);
    $exit = proc_close($process);
    $result = json_decode(trim((string)$stdout), true);
    if (!is_array($result)) {
        echo json_encode(['status' => 'redacted_child_error', 'exit' => $exit,
            'stderr' => $stderr === '' ? 'empty' : 'nonempty']), PHP_EOL;
        exit(2);
    }
    echo json_encode($result, JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit($exit);
}

if ((string)getenv('POC_APP_ROOT') !== $root
    || (string)getenv('POC_BASE_URL') !== $base
    || (string)getenv('POC_DIALECT') !== $dialect
    || is_link($root)
    || hash_file('sha256', $root . '/application/model/Sport.php') !== $sourceSha) {
    throw new RuntimeException('Sport source/runtime drift');
}
$command = (string)file_get_contents('/proc/' . $pid . '/cmdline');
if (strpos($command, $root . '/public') === false
    || strpos($command, '127.0.0.1:' . parse_url($base, PHP_URL_PORT)) === false) {
    throw new RuntimeException('HTTP PID drift');
}
$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('External boundary changed');
}
$runtime = require $root . '/application/database.php';
if ($dialect === 'mysql') {
    $local = require '/workspace/zhct/zhctproject/store/application/config/local/database.php';
    if (!is_array($local) || ($runtime['type'] ?? '') !== 'mysql') {
        throw new RuntimeException('Not code-local MySQL');
    }
    foreach (['hostname', 'hostport', 'database', 'username', 'password'] as $key) {
        if (($runtime[$key] ?? ($key === 'hostport' ? 3306 : null))
            !== ($local[$key] ?? ($key === 'hostport' ? 3306 : null))) {
            throw new RuntimeException('MySQL runtime/local mismatch');
        }
    }
    $dsn = 'mysql:host=' . $runtime['hostname'] . ';port=' . (int)($runtime['hostport'] ?? 3306)
        . ';dbname=' . $runtime['database'] . ';charset=' . ($runtime['charset'] ?? 'utf8');
} else {
    foreach (['hostname' => 'POC_HOST', 'hostport' => 'POC_PORT',
        'database' => 'POC_SCHEMA', 'username' => 'POC_USER',
        'password' => 'POC_PASSWORD'] as $key => $envKey) {
        if ((string)($runtime[$key] ?? '') !== (string)getenv($envKey)) {
            throw new RuntimeException('C runtime database mismatch');
        }
    }
    if ((string)getenv('POC_HOST') !== '127.0.0.1'
        || (string)getenv('POC_PORT') !== '5237'
        || (string)getenv('POC_SCHEMA') !== 'ZHCT_APP') {
        throw new RuntimeException('C nonproduction database mismatch');
    }
    $dsn = 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP';
}
$connect = static function () use ($dsn, $runtime): PDO {
    return new PDO($dsn, (string)$runtime['username'], (string)$runtime['password'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_TIMEOUT => 5]);
};
$sameIdentity = static function (PDO $pdo) use ($dialect): bool {
    if ($dialect === 'dm') {
        if (strtoupper((string)$pdo->query('SELECT USER FROM DUAL')->fetchColumn()) !== 'ZHCT_APP') {
            return false;
        }
        $value = 'ZHCT_APP|5237|' . (string)$pdo->query(
            "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn();
        return hash('sha256', $value)
            === 'bc98485aed26beab348770a82ae38dbf922b04ab88a210cd7282c4caf0156194';
    }
    $value = (string)$pdo->query(
        "SELECT CONCAT(@@server_uuid,'|',@@hostname,'|',@@port,'|',DATABASE())")->fetchColumn();
    return hash('sha256', $value)
        === '003eb5aecf1cd4595b609c1f7f20b9ee325b578309fb9fe14fedaf77584fa401';
};
$db = $connect();
if (!$sameIdentity($db)) throw new RuntimeException('Database identity changed');
$table = $dialect === 'dm' ? 'YDY_SPORT' : 'ydy_sport';
$userTable = $dialect === 'dm' ? 'YDY_USER' : 'ydy_user';
$queueTable = $dialect === 'dm' ? 'YDY_EXPORT_LOG' : 'ydy_export_log';
$sportSql = 'SELECT UUID,NAME,PUUID FROM ' . $table . ' ORDER BY '
    . ($dialect === 'dm'
        ? "NLSSORT(NAME,'NLS_SORT=SCHINESE_PINYIN_M')"
        : 'CONVERT(NAME USING gbk)');
$fetch = static function (PDO $pdo, string $sql, array $args = [], int $cap = 600): array {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($args);
    $result = [];
    while (($raw = $stmt->fetch(PDO::FETCH_ASSOC)) !== false) {
        if (count($result) >= $cap) throw new RuntimeException('Source cap exceeded');
        $row = [];
        foreach (array_change_key_case($raw, CASE_LOWER) as $key => $value) {
            $row[$key] = is_resource($value) ? stream_get_contents($value) : $value;
        }
        $result[] = $row;
    }
    return $result;
};
$scalar = static function (PDO $pdo, string $sql, array $args = []) {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($args);
    return $stmt->fetchColumn();
};
$source = $fetch($db, $sportSql);
if (count($source) !== 457) throw new RuntimeException('Sport source count changed');
$map = [];
$orderedUuids = [];
foreach ($source as $row) {
    $uuid = (string)$row['uuid'];
    if (isset($map[$uuid]) || !mb_check_encoding((string)$row['name'], 'UTF-8')) {
        throw new RuntimeException('Sport source identity/UTF8 invalid');
    }
    $map[$uuid] = $row;
    $orderedUuids[] = $uuid;
}
$sourceSha = hash('sha256', serialize($source));
$orderSha = hash('sha256', implode('|', $orderedUuids));
if ($dialect === 'mysql'
    && $orderSha !== 'f7febcc93d86d755a11d66422c6b929b5feae2292cf02bdb011b29f24f3996c7') {
    throw new RuntimeException('MySQL source order drift');
}
$queue = static function (PDO $pdo) use ($queueTable): array {
    $row = $pdo->query('SELECT COUNT(*) TOTAL,'
        . ' SUM(CASE WHEN STATUS=0 THEN 1 ELSE 0 END) PENDING FROM ' . $queueTable)
        ->fetch(PDO::FETCH_ASSOC);
    return [(int)$row['total'], (int)$row['pending']];
};
$queueBefore = $queue($db);
$oldActors = (int)$scalar($db, "SELECT COUNT(*) FROM $userTable WHERE ACCOUNT LIKE 'CDXSPT%'");
if ($queueBefore[1] !== 0 || $oldActors !== 0) {
    throw new RuntimeException('Queue or prior actor unavailable');
}
if ($mode === '--preflight') {
    echo json_encode(['status' => 'preflight_pass', 'lane' => $lane,
        'source_rows' => count($source), 'source_sha' => $sourceSha,
        'order_sha' => $orderSha, 'queue' => $queueBefore,
        'old_actor' => 0, 'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(0);
}

umask(0077);
$marker = 'CDXSPT' . strtoupper(bin2hex(random_bytes(5)));
if ((int)$scalar($db, "SELECT COUNT(*) FROM $userTable WHERE UUID=? OR ACCOUNT=?",
    [$marker, $marker]) !== 0) throw new RuntimeException('Marker collision');
$dir = sys_get_temp_dir() . '/cdx-sport-' . strtolower($lane) . '-' . $marker;
if (file_exists($dir) || !mkdir($dir, 0700)) throw new RuntimeException('Journal unavailable');
$journal = $dir . '/run.json';
$state = ['lane' => $lane, 'marker' => $marker, 'state' => 'before_write'];
$save = static function () use (&$state, $journal): void {
    if (file_put_contents($journal, json_encode($state)) === false) {
        throw new RuntimeException('Journal write failed');
    }
    chmod($journal, 0600);
};
$save();
$actorId = 0;
$httpSent = false;
$httpConfirmed = true;
$cleaned = false;
$sourceUnchanged = false;
$queueUnchanged = false;
$businessRows = 0;
$responseSha = '';
$error = '';
try {
    $stmt = $db->prepare('INSERT INTO ' . $userTable
        . ' (UUID,ACCOUNT,PWD,SALT,NAME,TYPE,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?)');
    $stmt->execute([$marker, $marker, 'DISABLED', strtoupper(bin2hex(random_bytes(8))),
        $marker, 0, date('Y-m-d H:i:s'), 'CODEX', 0]);
    if ($stmt->rowCount() !== 1) throw new RuntimeException('Actor insert affected rows mismatch');
    $actorId = (int)$scalar($db, 'SELECT ID FROM ' . $userTable
        . ' WHERE UUID=? AND ACCOUNT=? AND TYPE=0 AND CREATE_BY=?',
        [$marker, $marker, 'CODEX']);
    if ($actorId <= 0) throw new RuntimeException('Actor ID unavailable');
    $state['actor_id'] = $actorId;
    $state['state'] = 'actor_committed';
    $save();
    require_once $root . '/application/base/Security.php';
    $token = \app\base\Security::encrypt(implode("\t",
        [(string)$actorId, $marker, $marker, '0', '', '', $marker]));
    if (!is_string($token) || $token === '') throw new RuntimeException('Token unavailable');
    $context = stream_context_create(['http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/x-www-form-urlencoded\r\ntoken: " . $token . "\r\n",
        'content' => '', 'timeout' => 15, 'ignore_errors' => true,
        'follow_location' => 0, 'max_redirects' => 0,
    ]]);
    $httpSent = true;
    $httpConfirmed = false;
    $stream = @fopen($base . '/p/staff/sport', 'rb', false, $context);
    $body = is_resource($stream) ? stream_get_contents($stream, 1048577) : false;
    $meta = is_resource($stream) ? stream_get_meta_data($stream) : [];
    if (is_resource($stream)) fclose($stream);
    if (!is_string($body) || strlen($body) > 1048576 || !empty($meta['timed_out'])) {
        throw new RuntimeException('HTTP outcome unknown/oversized');
    }
    $httpConfirmed = true;
    $headers = $http_response_header ?? [];
    if (strpos((string)($headers[0] ?? ''), ' 200 ') === false) {
        throw new RuntimeException('HTTP status mismatch');
    }
    $json = json_decode($body, true);
    if (!is_array($json) || !isset($json['code'], $json['message'])
        || gettype($json['code']) !== 'integer'
        || gettype($json['message']) !== 'string'
        || (int)$json['code'] !== 0
        || !isset($json['data']) || !is_array($json['data'])) {
        throw new RuntimeException('Business response mismatch');
    }
    $seen = [];
    $walk = static function (array $nodes, string $parent) use (&$walk, &$seen, $map): void {
        foreach ($nodes as $node) {
            if (!is_array($node) || !isset($node['uuid'], $node['puuid'], $node['name'])
                || gettype($node['uuid']) !== 'string'
                || gettype($node['puuid']) !== 'string'
                || gettype($node['name']) !== 'string'
                || (string)$node['puuid'] !== $parent) {
                throw new RuntimeException('Tree parent/shape mismatch');
            }
            $uuid = (string)$node['uuid'];
            if (isset($seen[$uuid]) || !isset($map[$uuid])
                || (string)$node['name'] !== (string)$map[$uuid]['name']
                || (string)$node['puuid'] !== (string)$map[$uuid]['puuid']
                || !array_key_exists('chhildShow', $node)
                || $node['chhildShow'] !== false) {
                throw new RuntimeException('Sport row/source/type mismatch');
            }
            $seen[$uuid] = true;
            if (isset($node['children'])) $walk($node['children'], $uuid);
        }
    };
    $walk($json['data'], '0');
    $businessRows = count($seen);
    if ($businessRows !== 457) throw new RuntimeException('Sport tree row count mismatch');
    $responseSha = hash('sha256', $body);
    if ($dialect === 'mysql'
        && $responseSha !== '5ce835b0511ca86f4a25acc2280e645556026eea3018696b912f8b1b31595d13') {
        throw new RuntimeException('A/B full response contract mismatch');
    }
    $after = $connect();
    if (!$sameIdentity($after)
        || hash('sha256', serialize($fetch($after, $sportSql))) !== $sourceSha) {
        throw new RuntimeException('Sport source changed');
    }
    $sourceUnchanged = true;
    $queueUnchanged = $queue($after) === $queueBefore;
    if (!$queueUnchanged) throw new RuntimeException('Export queue changed');
    $state['state'] = 'http_confirmed';
    $save();
} catch (Throwable $exception) {
    $error = get_class($exception) . ':' . hash('sha256', $exception->getMessage());
} finally {
    if (!$httpSent || $httpConfirmed) {
        try {
            $cleanup = $connect();
            if (!$sameIdentity($cleanup)) throw new RuntimeException('Cleanup DB identity mismatch');
            $owners = $fetch($cleanup, 'SELECT ID,UUID,ACCOUNT,NAME,TYPE,CREATE_BY'
                . ' FROM ' . $userTable . ' WHERE UUID=? OR ACCOUNT=?', [$marker, $marker], 2);
            if (count($owners) !== 1 || (int)$owners[0]['id'] !== $actorId
                || (string)$owners[0]['uuid'] !== $marker
                || (string)$owners[0]['account'] !== $marker
                || (string)$owners[0]['name'] !== $marker
                || (int)$owners[0]['type'] !== 0
                || (string)$owners[0]['create_by'] !== 'CODEX') {
                throw new RuntimeException('Actor ownership mismatch');
            }
            foreach (['user_role', 'user_data'] as $suffix) {
                $relation = $dialect === 'dm' ? 'YDY_' . strtoupper($suffix) : 'ydy_' . $suffix;
                if ((int)$scalar($cleanup, 'SELECT COUNT(*) FROM ' . $relation
                    . ' WHERE USER_UUID=?', [$marker]) !== 0) {
                    throw new RuntimeException('Unexpected actor relation');
                }
            }
            $cleanup->beginTransaction();
            $delete = $cleanup->prepare('DELETE FROM ' . $userTable
                . ' WHERE ID=? AND UUID=? AND ACCOUNT=? AND NAME=? AND TYPE=0 AND CREATE_BY=?');
            $delete->execute([$actorId, $marker, $marker, $marker, 'CODEX']);
            if ($delete->rowCount() !== 1) {
                $cleanup->rollBack();
                throw new RuntimeException('Actor delete mismatch');
            }
            $cleanup->commit();
            $zero = $connect();
            if (!$sameIdentity($zero)
                || (int)$scalar($zero, 'SELECT COUNT(*) FROM ' . $userTable
                    . ' WHERE UUID=? OR ACCOUNT=?', [$marker, $marker]) !== 0) {
                throw new RuntimeException('Independent actor zero failed');
            }
            $cleaned = true;
            unlink($journal);
            rmdir($dir);
        } catch (Throwable $exception) {
            $error .= ';cleanup:' . hash('sha256', $exception->getMessage());
        }
    } else {
        $error .= ';unknown_http_keep_journal';
    }
}
echo json_encode(['status' => $error === '' ? 'pass' : 'stop', 'lane' => $lane,
    'business_rows' => $businessRows, 'response_sha' => $responseSha,
    'source_order_sha' => $orderSha, 'source_unchanged' => $sourceUnchanged,
    'queue_unchanged' => $queueUnchanged, 'actor_independent_zero' => $cleaned,
    'journal_removed' => $cleaned, 'failure' => $error], JSON_UNESCAPED_SLASHES), PHP_EOL;
if ($error !== '' || !$cleaned) exit(1);
