<?php

declare(strict_types=1);

$lane = $argv[1] ?? '';
if (!in_array($lane, ['B', 'C'], true)) exit(2);
$root = '/tmp/cdx-prepay-code-scan-20261012-' . $lane . '-ai';
$dialect = $lane === 'B' ? 'mysql' : 'dm';
$payCase = getenv('POC_CASE') === 'PAY';
$refundCase = getenv('POC_CASE') === 'REFUND';
$marker = $refundCase
    ? ($lane === 'B' ? 'CDXPPREF12B01-O1' : 'CDXPPREF12C01-O1')
    : ($payCase
        ? ($lane === 'B' ? 'CDXPPPAY12B01' : 'CDXPPPAY12C01')
        : ($lane === 'B' ? 'CDXPP121012B01' : 'CDXPP121012C01'));
$aiUserId = $refundCase ? ($lane === 'B' ? 46921 : 1936) : ($lane === 'B' ? 46916 : 1931);
$storeUserId = $refundCase ? ($lane === 'B' ? 99700 : 2799) : ($lane === 'B' ? 99689 : 2792);
$expectedStoreId = $refundCase ? 10001 : 2147001111;
$requestHost = $refundCase ? 'prepay.refund10001.invalid' : 'prepay.code-scan.invalid';
$storeHostMap = ['prepay.code-scan.invalid' => 2147001111,
    'prepay.refund10001.invalid' => 10001];
if (!is_file($root . '/app/api/service/PrepayOrder.php')
    || !is_file($root . '/vendor/autoload.php')) throw new RuntimeException('Private AI source missing');
if ($lane === 'B') {
    $envPath = '/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local';
    $ini = parse_ini_file($envPath, true, INI_SCANNER_RAW);
    if (!is_array($ini) || empty($ini['DATABASE']) || empty($ini['DATABASE_ZHCT'])) {
        throw new RuntimeException('AI local dev env missing');
    }
    foreach (['HOST' => 'HOSTNAME', 'PORT' => 'HOSTPORT', 'SCHEMA' => 'DATABASE',
        'USER' => 'USERNAME', 'PASSWORD' => 'PASSWORD'] as $target => $source) {
        putenv('POC_AI_' . $target . '=' . ($ini['DATABASE'][$source] ?? ($target === 'PORT' ? '3306' : '')));
        putenv('POC_' . $target . '=' . ($ini['DATABASE_ZHCT'][$source] ?? ($target === 'PORT' ? '3306' : '')));
    }
    putenv('POC_DIALECT=mysql');
    $storeLocal = require '/Users/wiscourper/Documents/workspace/zhct/zhctproject/store/application/config/local/database.php';
    foreach (['HOST' => 'hostname', 'PORT' => 'hostport', 'SCHEMA' => 'database',
        'USER' => 'username', 'PASSWORD' => 'password'] as $suffix => $field) {
        $default = $suffix === 'PORT' ? '3306' : '';
        if ((string)getenv('POC_' . $suffix) !== (string)($storeLocal[$field] ?? $default)
            || (string)getenv('POC_AI_' . $suffix) !== (string)($storeLocal['ai_zhct'][$field] ?? $default)) {
            throw new RuntimeException('AI .env.local differs from Store local development DB');
        }
    }
} else {
    $pid = (int)file_get_contents($root . '/private-http.pid');
    if (strpos((string)file_get_contents('/proc/' . $pid . '/cmdline'), $root . '/public') === false) {
        throw new RuntimeException('Wrong private AI HTTP process');
    }
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $pair) {
        $parts = explode('=', $pair, 2);
        if (count($parts) === 2 && strpos($parts[0], 'POC_') === 0) putenv($pair);
    }
}
foreach (['POC_HOST', 'POC_PORT', 'POC_SCHEMA', 'POC_USER', 'POC_PASSWORD',
    'POC_AI_HOST', 'POC_AI_PORT', 'POC_AI_SCHEMA', 'POC_AI_USER', 'POC_AI_PASSWORD'] as $key) {
    if ((string)getenv($key) === '') throw new RuntimeException('AI DB env incomplete');
}
$connect = static function (bool $primary) use ($dialect): PDO {
    $prefix = $primary ? 'POC_AI_' : 'POC_';
    $host = getenv($prefix . 'HOST');
    $port = getenv($prefix . 'PORT');
    $schema = getenv($prefix . 'SCHEMA');
    $dsn = $dialect === 'dm'
        ? "dm:host=$host;port=$port;schema=$schema"
        : "mysql:host=$host;port=$port;dbname=$schema;charset=utf8mb4";
    return new PDO($dsn, getenv($prefix . 'USER'), getenv($prefix . 'PASSWORD'),
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
};
$ai = $connect(true);
$store = $connect(false);
$table = static function (string $name, bool $isAi = false) use ($dialect): string {
    $prefix = $isAi ? 'yoshop_' : 'ydy_';
    return $dialect === 'dm' ? strtoupper($prefix . $name) : $prefix . $name;
};
$rows = static function (PDO $db, string $sql, array $params = []): array {
    $stmt = $db->prepare($sql);
    $stmt->execute($params);
    return array_map(static function (array $row): array {
        return array_change_key_case($row, CASE_LOWER);
    }, $stmt->fetchAll(PDO::FETCH_ASSOC));
};
$registerPrivateAutoload = static function (string $sourceRoot): void {
    spl_autoload_register(static function (string $class) use ($sourceRoot): void {
        foreach (['app\\' => 'app/', 'cores\\' => 'cores/'] as $prefix => $directory) {
            if (strpos($class, $prefix) !== 0) continue;
            $file = $sourceRoot . '/' . $directory
                . str_replace('\\', '/', substr($class, strlen($prefix))) . '.php';
            if (is_file($file)) require_once $file;
            return;
        }
    }, true, true);
};
$aiUserRows = $rows($ai, 'SELECT * FROM ' . $table('user', true) . ' WHERE USER_ID=?', [$aiUserId]);
$storeUserRows = $rows($store, 'SELECT * FROM ' . $table('user') . ' WHERE ID=?', [$storeUserId]);
if (count($aiUserRows) !== 1 || count($storeUserRows) !== 1) {
    throw new RuntimeException('Synthetic linked users missing');
}
$aiUser = $aiUserRows[0];
$storeUser = $storeUserRows[0];
if ((int)$aiUser['store_id'] !== $expectedStoreId || (int)$aiUser['is_delete'] !== 0
    || (int)$storeUser['ai_user_id'] !== $aiUserId || (int)$storeUser['is_delete'] !== 0
    || strpos((string)$storeUser['account'], 'CDX') !== 0) {
    throw new RuntimeException('Synthetic linked identity mismatch');
}
$storeRows = $rows($ai, 'SELECT STORE_ID,IS_DELETE,IS_RECYCLE FROM ' . $table('store', true)
    . ' WHERE STORE_ID=?', [$expectedStoreId]);
if (count($storeRows) !== 1 || (int)$storeRows[0]['is_delete'] !== 0
    || (int)$storeRows[0]['is_recycle'] !== 0) {
    throw new RuntimeException('Synthetic miniapp store unavailable');
}
$prepayRows = $rows($store, 'SELECT * FROM ' . $table('prepay_order')
    . ' WHERE DEVICE_ORDER_NO=?', [$marker]);
if (getenv('POC_DRY_RUN') === '1') {
    $ownedDeviceId = $lane === 'B' ? 232 : 178;
    $ownedDevice = $rows($store, 'SELECT RESTAURANT_ID FROM '
        . $table('equipment') . ' WHERE ID=?', [$ownedDeviceId]);
    $restaurant = count($ownedDevice) === 1 ? $rows($store,
        'SELECT STALL_TYPE FROM ' . $table('restaurants') . ' WHERE ID=?',
        [(int)$ownedDevice[0]['restaurant_id']]) : [];
    $observedPrepay = count($prepayRows) === 1 ? $prepayRows[0] : [];
    $observedMealId = (int)($observedPrepay['meal_order_id'] ?? 0);
    $observedMeals = $observedMealId > 0 ? $rows($store, 'SELECT ID,PAY_STATUS,ORDER_STATUS,UPDATE_TIME FROM '
        . $table('meal_order') . ' WHERE ID=?', [$observedMealId]) : [];
    $bindSettings = $rows($store, 'SELECT CONFIG_VALUE FROM '
        . $table('config') . " WHERE CONFIG_KEY='bind_time' AND STATUS=1");
    echo json_encode(['lane' => $lane, 'linked_ai_user' => $aiUserId,
        'linked_store_user' => $storeUserId, 'synthetic_store' => $expectedStoreId,
        'wallet_cash' => (string)$aiUser['balance'],
        'wallet_subsidy' => (string)$aiUser['subsidy_balance'],
        'wallet_gift' => (string)$aiUser['gift_balance'],
        'owned_restaurant_stall_type' => isset($restaurant[0]['stall_type'])
            ? (int)$restaurant[0]['stall_type'] : null,
        'prepay_marker_rows' => count($prepayRows),
        'prepay_id' => (int)($observedPrepay['id'] ?? 0),
        'prepay_status' => (int)($observedPrepay['prepay_status'] ?? -1),
        'prepay_expire_time' => (string)($observedPrepay['expire_time'] ?? ''),
        'prepay_update_by' => (string)($observedPrepay['update_by'] ?? ''),
        'prepay_update_time' => (string)($observedPrepay['update_time'] ?? ''),
        'prepay_meal_order_id' => $observedMealId,
        'prepay_user_id' => (int)($observedPrepay['user_id'] ?? -1),
        'linked_meal_rows' => count($observedMeals), 'no_HTTP_or_DML' => true,
        'meal_pay_status' => isset($observedMeals[0]['pay_status'])
            ? (int)$observedMeals[0]['pay_status'] : null,
        'meal_order_status' => isset($observedMeals[0]['order_status'])
            ? (int)$observedMeals[0]['order_status'] : null,
        'meal_update_time' => (string)($observedMeals[0]['update_time'] ?? ''),
        'active_bind_time_values' => array_values(array_unique(array_map(
            static function (array $row): string { return (string)$row['config_value']; },
            $bindSettings))),
        'ai_service_sha256' => hash_file('sha256', $root . '/app/api/service/PrepayOrder.php')]) . PHP_EOL;
    exit(0);
}
if (getenv('POC_AUTH_PREFLIGHT') === '1') {
    $_SERVER['HTTP_HOST'] = $requestHost;
    $_SERVER['HTTP_PLATFORM'] = 'POC';
    define('TP_ENV', 'poc');
    define('BUSINESS', 'aizhct');
    define('ENTERPRISE_NO', '10001');
    require $root . '/vendor/autoload.php';
    $registerPrivateAutoload($root);
    $testApp = (new \think\App($root))->setEnvName('poc');
    $testApp->initialize();
    $testApp->env->set('store.host', $storeHostMap);
    require_once $root . '/app/api/common.php';
    $probeToken = 'CDXPP12AUTH_' . $lane . '_' . bin2hex(random_bytes(20));
    $probeProfile = ['user' => ['user_id' => $aiUserId],
        'password_version' => (int)($storeUser['password_version'] ?? $aiUser['password_version'] ?? 0),
        'login_type' => 'thirdparty'];
    if (!$testApp->cache->set($probeToken, $probeProfile, 60)) {
        throw new RuntimeException('Private FileCache auth probe failed');
    }
    try {
        $verified = \app\api\model\User::getUserByToken($probeToken);
        $passed = $verified && (int)$verified['user_id'] === $aiUserId
            && (int)$verified['store_id'] === $expectedStoreId;
    } finally {
        $testApp->cache->delete($probeToken);
    }
    $oneCard = (new \app\common\service\onecard\UserGuard())
        ->isOneCardUserByZhctIdentity($storeUserId, (string)$storeUser['staff_uuid']);
    echo json_encode(['lane' => $lane, 'original_UserModel_auth' => (bool)$passed,
        'one_card_user' => $oneCard, 'token_removed' => true,
        'HTTP' => 0, 'business_DML' => 0]) . PHP_EOL;
    exit($passed ? 0 : 1);
}
if (count($prepayRows) !== 1) {
    throw new RuntimeException('Scan requires exactly one owned prepay marker');
}
$attemptOrdinal = 1;
$basename = $refundCase ? 'refund-scan' : ($payCase ? 'pay-scan' : 'scan');
$attemptFile = $root . '/' . $basename . '-attempt.json';
$receiptFile = $root . '/' . $basename . '-receipt.json';
if (is_file($attemptFile)) {
    $prior = is_file($receiptFile)
        ? json_decode((string)file_get_contents($receiptFile), true) : null;
    if ($payCase || $refundCase || $lane !== 'C' || getenv('POC_RETRY_ZERO_WRITE') !== '1'
        || !is_array($prior) || ($prior['marker'] ?? '') !== $marker
        || (int)($prior['response_status'] ?? 0) !== 500
        || (int)($prior['meal_order_rows'] ?? -1) !== 0
        || (int)($prior['meal_order_id'] ?? -1) !== 0
        || (int)($prior['ai_settlement_rows'] ?? -1) !== 0
        || ($prior['wallet_unchanged'] ?? false) !== true) {
        throw new RuntimeException('Prior scan is not proven zero-write; no replay');
    }
    $attemptOrdinal = 2;
    $attemptFile = $root . '/scan-attempt2.json';
    $receiptFile = $root . '/scan-receipt2.json';
}
if (is_file($attemptFile) || is_file($receiptFile)) {
    throw new RuntimeException('This scan attempt already consumed');
}
$prepay = $prepayRows[0];
if ((int)$prepay['prepay_status'] !== 2 || (int)$prepay['meal_order_id'] !== 0
    || (int)$prepay['user_id'] !== 0 || (int)$prepay['price'] !== 1) {
    throw new RuntimeException('Only an unbound one-cent pending prepay can be scanned');
}
$walletBefore = [(string)$aiUser['balance'], (string)$aiUser['subsidy_balance'],
    (string)$aiUser['gift_balance']];
$_SERVER['HTTP_HOST'] = $requestHost;
$_SERVER['HTTP_PLATFORM'] = 'POC';
define('TP_ENV', 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require $root . '/vendor/autoload.php';
$registerPrivateAutoload($root);
$app = (new \think\App($root))->setEnvName('poc');
$app->initialize();
$app->env->set('store.host', $storeHostMap);
require_once $root . '/app/api/common.php';
$token = 'CDXPP121012_' . $lane . '_' . bin2hex(random_bytes(20));
$passwordVersion = (int)($storeUser['password_version'] ?? $aiUser['password_version'] ?? 0);
$profile = ['user' => ['user_id' => $aiUserId],
    'password_version' => $passwordVersion, 'login_type' => 'thirdparty'];
if (!$app->cache->set($token, $profile, 300)) throw new RuntimeException('Private FileCache token failed');
$authUser = \app\api\model\User::getUserByToken($token);
if (!$authUser || (int)$authUser['user_id'] !== $aiUserId
    || (int)$authUser['store_id'] !== $expectedStoreId) {
    $app->cache->delete($token);
    throw new RuntimeException('Actual user token validation failed');
}
$body = json_encode(['torderid' => (int)$prepay['id']], JSON_UNESCAPED_SLASHES);
$attempt = ['lane' => $lane, 'marker' => $marker, 'attempt_ordinal' => $attemptOrdinal,
    'prepay_id' => (int)$prepay['id'],
    'route' => '/api/PrepayOrder/scan', 'body_sha256' => hash('sha256', $body),
    'token_sha256' => hash('sha256', $token),
    'source_service_sha256' => hash_file('sha256', $root . '/app/api/service/PrepayOrder.php'),
    'before_meal_order_id' => 0, 'at' => date('c')];
$handle = fopen($attemptFile, 'xb');
if (!$handle) throw new RuntimeException('Could not lock scan once');
fwrite($handle, json_encode($attempt, JSON_UNESCAPED_SLASHES));
fclose($handle);
chmod($attemptFile, 0600);
$base = 'http://127.0.0.1:' . ($lane === 'B' ? 19133 : 19134);
$boundary = json_decode((string)file_get_contents($base . '/__poc_boundary'), true);
if (empty($boundary['external_stubs'])
    || ($boundary[$refundCase ? 'host_map_refund' : 'host_map'] ?? '') !== $requestHost) {
    throw new RuntimeException('Private AI HTTP boundary unavailable');
}
$context = stream_context_create(['http' => [
    'method' => 'POST', 'ignore_errors' => true, 'timeout' => 45,
    'header' => "Content-Type: application/json\r\nHost: " . $requestHost . "\r\n"
        . "platform: POC\r\nAccess-Token: " . $token . "\r\n",
    'content' => $body,
]]);
$started = microtime(true);
try {
    $wire = file_get_contents($base . '/api/PrepayOrder/scan', false, $context);
} finally {
    $app->cache->delete($token);
}
$duration = microtime(true) - $started;
$httpStatus = 0;
foreach (($http_response_header ?? []) as $header) {
    if (preg_match('/^HTTP\/\S+ (\d{3})/', $header, $match)) $httpStatus = (int)$match[1];
}
$response = is_string($wire) ? json_decode($wire, true) : null;
$afterPrepay = $rows($store, 'SELECT * FROM ' . $table('prepay_order')
    . ' WHERE ID=?', [(int)$prepay['id']]);
$updated = count($afterPrepay) === 1 ? $afterPrepay[0] : [];
$mealOrderId = (int)($updated['meal_order_id'] ?? 0);
$orders = $mealOrderId > 0 ? $rows($store, 'SELECT * FROM ' . $table('meal_order')
    . ' WHERE ID=?', [$mealOrderId]) : [];
$order = count($orders) === 1 ? $orders[0] : [];
$walletAfterRows = $rows($ai, 'SELECT BALANCE,SUBSIDY_BALANCE,GIFT_BALANCE FROM '
    . $table('user', true) . ' WHERE USER_ID=?', [$aiUserId]);
$walletAfter = count($walletAfterRows) === 1
    ? [(string)$walletAfterRows[0]['balance'],
        (string)$walletAfterRows[0]['subsidy_balance'],
        (string)$walletAfterRows[0]['gift_balance']]
    : [];
$settlementRows = $order ? $rows($ai, 'SELECT SETTLEMENT_ID FROM '
    . $table('account_settlement', true) . ' WHERE BIZ_ORDER_NO=?', [(string)$order['order_no']]) : [];
$receipt = [
    'lane' => $lane, 'marker' => $marker,
    'attempt_ordinal' => $attemptOrdinal, 'http_status' => $httpStatus,
    'response_json' => is_array($response), 'response_status' => $response['status'] ?? null,
    'response_keys' => is_array($response) ? array_keys($response) : [],
    'response_data_types' => is_array($response['data'] ?? null)
        ? array_map('gettype', $response['data']) : [],
    'response_order_id_match' => (int)($response['data']['orderId'] ?? 0) === $mealOrderId,
    'response_is_pay_one' => (int)($response['data']['isPay'] ?? -1) === 1,
    'body_sha256' => hash('sha256', (string)$wire),
    'duration_seconds' => round($duration, 3),
    'prepay_rows' => count($afterPrepay), 'meal_order_rows' => count($orders),
    'prepay_id' => (int)$prepay['id'], 'meal_order_id' => $mealOrderId,
    'prepay_user_match' => (int)($updated['user_id'] ?? 0) === $storeUserId,
    'prepay_order_no_match' => $order
        && (string)$updated['meal_order_no'] === (string)$order['order_no'],
    'pending_meal_source' => $order && (int)$order['source'] === 8
        && (int)$order['pay_status'] === 10
        && bccomp((string)$order['pay_price'], '0.01', 2) === 0
        && (int)$order['user_id'] === $storeUserId
        && (int)$order['restaurant_id'] === (int)$prepay['restaurant_id'],
    'wallet_unchanged' => $walletBefore === $walletAfter,
    'ai_settlement_rows' => count($settlementRows),
];
$receipt['passed'] = $receipt['http_status'] === 200
    && $receipt['response_json'] && (int)$receipt['response_status'] === 200
    && $receipt['response_order_id_match'] && $receipt['response_is_pay_one']
    && $receipt['prepay_rows'] === 1 && $receipt['meal_order_rows'] === 1
    && $receipt['prepay_user_match'] && $receipt['prepay_order_no_match']
    && $receipt['pending_meal_source'] && $receipt['wallet_unchanged']
    && $receipt['ai_settlement_rows'] === 0;
file_put_contents($receiptFile, json_encode($receipt,
    JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
chmod($receiptFile, 0600);
echo json_encode($receipt, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
exit($receipt['passed'] ? 0 : 1);
