<?php

declare(strict_types=1);

// SELECT-only A/B/C User/qrcode source and side-effect inventory.
$stage = 'default_deny';
set_exception_handler(static function (Throwable $error) use (&$stage): void {
    echo json_encode(['status' => 'redacted_error', 'stage' => $stage,
        'error_class' => get_class($error), 'code' => (string)$error->getCode(),
        'business_http_sent' => false, 'dml_sent' => false,
        'image_created' => false], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(2);
});
set_error_handler(static function (int $severity): bool {
    if (!(error_reporting() & $severity)) return false;
    throw new ErrorException('redacted warning', 0, $severity);
});
$lane = (string)getenv('CDX_USER_QR_LANE');
if (!in_array($lane, ['A', 'B', 'C'], true)
    || count($argv) !== 2 || ($argv[1] ?? '') !== '--preflight-only'
    || (string)getenv('CDX_USER_QR_PREFLIGHT')
        !== 'ROOT_APPROVED_QR_SOURCE_READ_V1'
    || (string)getenv('CDX_USER_QR_SHA')
        !== hash_file('sha256', __FILE__)) {
    echo json_encode(['status' => 'default-deny',
        'db_connected' => false, 'business_http_sent' => false,
        'image_created' => false, 'dml_sent' => false]), PHP_EOL;
    exit(78);
}
if (PHP_VERSION_ID < 70400 || PHP_VERSION_ID >= 70500) {
    throw new RuntimeException('AI PHP7.4 CLI required');
}
$stage = 'runtime_source_identity';
$targets = [
    'A' => ['/private/tmp/zhct-http-ai-macos.zjaU8h', 18105, 13109, 'mysql',
        '3c28c1b29b07b642d13372ed3ae867b9b1c0ff2c69babb77f73835ae99ae4e50'],
    'B' => ['/private/tmp/zhct-http-ai-macos.x2CCZX', 18101, 13127, 'mysql',
        '7834cf8b84369382ea5d6ce0eed946c6ebe3224d7f7fcb53085c14a7cbb1f471'],
    'C' => ['/tmp/zhct-ai-http-20260929.3XDj3w', 18102, 53772, 'dm',
        '7834cf8b84369382ea5d6ce0eed946c6ebe3224d7f7fcb53085c14a7cbb1f471'],
];
[$root, $httpPort, $httpPid, $dialect, $controllerSha] = $targets[$lane];
if (is_link($root)
    || hash_file('sha256', $root . '/app/api/controller/User.php')
        !== $controllerSha
    || hash_file('sha256', $root . '/app/api/service/User.php')
        !== '1fd7986c12b23a86f00721b948832b48fdd75da378f42982a1ea355fe7d6a8e3'
    || hash_file('sha256', $root . '/app/common/service/Qrcode.php')
        !== 'ed3d9f61c7693d874972a1263b228e89112e603ff5cd1696a67d4e6847b9b631'
    || hash_file('sha256', $root . '/app/store/service/Upload.php')
        !== 'fd3bba82e59e79ed51e0ed17961e396db2242a57684635f4c001a1da2bbf2c64') {
    throw new RuntimeException('Approved User/qrcode source changed');
}
$config = [];
if ($lane === 'C') {
    $cmd = (string)file_get_contents('/proc/' . $httpPid . '/cmdline');
    $raw = (string)file_get_contents('/proc/' . $httpPid . '/environ');
    if (strpos($cmd, '127.0.0.1:' . $httpPort) === false
        || strpos($cmd, $root . '/public') === false
        || $raw === '' || strpos($raw, 'PHP_CLI_SERVER_WORKERS=') !== false) {
        throw new RuntimeException('C AI isolated listener drift');
    }
    foreach (explode("\0", $raw) as $entry) {
        $at = strpos($entry, '=');
        if ($at !== false && strpos($entry, 'POC_') === 0) {
            $config[substr($entry, 0, $at)] = substr($entry, $at + 1);
        }
    }
    foreach (['POC_AI_HOST' => '127.0.0.1', 'POC_AI_PORT' => '5238',
        'POC_AI_SCHEMA' => 'AIZHCT_APP', 'POC_AI_USER' => 'AIZHCT_APP',
        'POC_HOST' => '127.0.0.1', 'POC_PORT' => '5237',
        'POC_SCHEMA' => 'ZHCT_APP'] as $key => $expected) {
        if (($config[$key] ?? null) !== $expected) {
            throw new RuntimeException('C Store/AI nonproduction target mismatch');
        }
    }
    $machineId = trim((string)file_get_contents('/etc/machine-id'));
    if ($machineId === '' || gethostname() === false
        || hash('sha256', gethostname() . '|' . $machineId)
            !== 'd56d81988bb0f106946cce91c608fdd0ef1116e5150b23376ea87e086eace4b9') {
        throw new RuntimeException('C DM host identity mismatch');
    }
} else {
    $pids = array_values(array_filter(explode("\n", trim((string)shell_exec(
        '/usr/sbin/lsof -t -nP -iTCP:' . $httpPort
        . ' -sTCP:LISTEN 2>/dev/null'))),
        static function (string $v): bool { return ctype_digit($v); }));
    if (count($pids) !== 1 || (int)$pids[0] !== $httpPid) {
        throw new RuntimeException('A/B AI listener PID changed');
    }
    $cmd = (string)shell_exec('/bin/ps eww -p ' . $httpPid . ' -o command=');
    if (strpos($cmd, '-S 127.0.0.1:' . $httpPort) === false
        || (strpos($cmd, $root . '/public') === false
            && strpos($cmd,
                str_replace('/private/tmp/', '/tmp/', $root) . '/public') === false)
        || strpos($cmd, 'PHP_CLI_SERVER_WORKERS=') !== false) {
        throw new RuntimeException('A/B AI isolated root drift');
    }
    $localEnv = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local';
    if (realpath($localEnv) !== $localEnv) {
        throw new RuntimeException('Code-local AI env absent');
    }
    $ini = parse_ini_file($localEnv, true, INI_SCANNER_RAW);
    if (!is_array($ini) || !isset($ini['DATABASE'], $ini['DATABASE_ZHCT'])) {
        throw new RuntimeException('Two code-local database sections absent');
    }
    foreach (['DATABASE' => 'POC_AI_', 'DATABASE_ZHCT' => 'POC_']
        as $section => $prefix) {
        foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT',
            'DATABASE' => 'SCHEMA', 'USERNAME' => 'USER',
            'PASSWORD' => 'PASSWORD'] as $field => $suffix) {
            $key = $prefix . $suffix;
            $value = (string)($ini[$section][$field] ?? '');
            if ($value === '' || preg_match('/(?:^|\s)'
                . preg_quote($key . '=' . $value, '/') . '(?=\s|$)/', $cmd) !== 1) {
                throw new RuntimeException('A/B AI runtime differs from code local');
            }
            $config[$key] = $value;
        }
    }
}
$boundary = json_decode((string)file_get_contents(
    'http://127.0.0.1:' . $httpPort . '/__poc_boundary', false,
    stream_context_create(['http' => ['timeout' => 8,
        'follow_location' => 0, 'ignore_errors' => true]])), true);
if (!is_array($boundary) || ($boundary['external_stubs'] ?? null) !== true) {
    throw new RuntimeException('AI external stub boundary absent');
}
foreach (['POC_AI_HOST', 'POC_AI_PORT', 'POC_AI_SCHEMA', 'POC_AI_USER',
    'POC_AI_PASSWORD', 'POC_HOST', 'POC_PORT', 'POC_SCHEMA',
    'POC_USER', 'POC_PASSWORD'] as $key) {
    if (empty($config[$key])) throw new RuntimeException('Approved DB config incomplete');
}
$stage = 'database_connect';
$make = static function (string $prefix) use ($dialect, $config): PDO {
    $dsn = $dialect === 'dm'
        ? 'dm:host=' . $config[$prefix . 'HOST'] . ';port=' . $config[$prefix . 'PORT']
            . ';schema=' . $config[$prefix . 'SCHEMA']
        : 'mysql:host=' . $config[$prefix . 'HOST'] . ';port=' . $config[$prefix . 'PORT']
            . ';dbname=' . $config[$prefix . 'SCHEMA'] . ';charset=utf8';
    return new PDO($dsn, $config[$prefix . 'USER'], $config[$prefix . 'PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
};
$ai = $make('POC_AI_');
$store = $make('POC_');
$stage = 'database_identity';
foreach ([[$ai, 'POC_AI_SCHEMA'], [$store, 'POC_SCHEMA']] as $targetDb) {
    [$db, $schemaKey] = $targetDb;
    $actual = $dialect === 'dm'
        ? [strtoupper((string)$db->query('SELECT USER FROM DUAL')->fetchColumn()),
            strtoupper((string)$db->query(
                "SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn())]
        : [(string)$db->query('SELECT DATABASE()')->fetchColumn()];
    $expected = $dialect === 'dm'
        ? [strtoupper($config[$schemaKey]), strtoupper($config[$schemaKey])]
        : [$config[$schemaKey]];
    if ($actual !== $expected) throw new RuntimeException('Database identity drift');
}
$stage = 'schema_presence';
$tables = [
    [$store, 'ydy_user', 'id,ai_user_id,is_delete'],
    [$store, 'ydy_user_code', 'id,type,user_id,paycode,file_id,file_path,is_delete'],
    [$store, 'ydy_config', 'config_key,config_value'],
    [$ai, 'yoshop_user', 'user_id,store_id,is_delete'],
    [$ai, 'yoshop_store_setting', 'store_id'],
    [$ai, 'yoshop_upload_file', 'file_id,store_id,storage,file_path'],
];
foreach ($tables as $entry) {
    [$db, $table, $columns] = $entry;
    $db->query('SELECT ' . $columns . ' FROM ' . $table . ' WHERE 1=0');
}
$stage = 'aggregate_inventory';
$count = static function (PDO $db, string $sql): int {
    return (int)$db->query($sql)->fetchColumn();
};
$mapped = $count($store,
    'SELECT COUNT(*) FROM ydy_user WHERE ai_user_id > 0 AND is_delete = 0');
$codes = $count($store,
    'SELECT COUNT(*) FROM ydy_user_code WHERE type = 4 AND is_delete = 0');
$mappedWithCode = $count($store,
    'SELECT COUNT(*) FROM ydy_user u WHERE u.ai_user_id > 0 AND u.is_delete = 0'
    . ' AND EXISTS (SELECT 1 FROM ydy_user_code c WHERE c.user_id = u.id'
    . ' AND c.type = 4 AND c.is_delete = 0)');
$configQuery = $store->query("SELECT config_key,config_value FROM ydy_config "
    . "WHERE config_key IN ('paycode_file','paycode_expires_in')");
$paycodeFile = null;
$expiresIn = null;
foreach ($configQuery->fetchAll(PDO::FETCH_ASSOC) as $row) {
    $key = strtolower((string)($row['config_key'] ?? $row['CONFIG_KEY'] ?? ''));
    $value = (string)($row['config_value'] ?? $row['CONFIG_VALUE'] ?? '');
    if ($key === 'paycode_file') $paycodeFile = $value;
    if ($key === 'paycode_expires_in') $expiresIn = $value;
}
$settingSql = $dialect === 'dm'
    ? 'SELECT "VALUES" FROM yoshop_store_setting WHERE "KEY" = ? AND store_id = ?'
    : 'SELECT `values` FROM yoshop_store_setting WHERE `key` = ? AND store_id = ?';
$statement = $ai->prepare($settingSql);
$statement->execute(['storage', 10001]);
$settings = $statement->fetchAll(PDO::FETCH_NUM);
if (count($settings) > 1) throw new RuntimeException('Storage setting not unique');
$storageDefault = 'code_default_local';
if ($settings !== []) {
    $decoded = json_decode((string)$settings[0][0], true);
    if (!is_array($decoded)) throw new RuntimeException('Storage setting invalid JSON');
    $storageDefault = (string)($decoded['default'] ?? 'code_default_local');
}
if (!in_array($storageDefault,
    ['local', 'qiniu', 'aliyun', 'qcloud', 'code_default_local'], true)) {
    throw new RuntimeException('Unknown storage driver');
}
echo json_encode([
    'status' => 'select_only_pass', 'lane' => $lane,
    'db_identity' => $dialect === 'dm' ? 'approved_nonproduction_dm' : 'code_local_dev_mysql',
    'mapped_user_count' => $mapped, 'active_type4_code_count' => $codes,
    'mapped_user_with_existing_type4_code_count' => $mappedWithCode,
    'paycode_file' => $paycodeFile ?? 'code_default_1',
    'paycode_expires_in' => $expiresIn ?? 'code_default_60',
    'ai_storage_default' => $storageDefault,
    'mapped_positive_without_write_possible' => false,
    'business_http_sent' => false, 'dml_sent' => false,
    'image_created' => false,
], JSON_UNESCAPED_SLASHES), PHP_EOL;
