<?php

declare(strict_types=1);

require __DIR__ . '/contract_shape.php';

$dialect = (string)getenv('POC_DIALECT');
$root = rtrim((string)getenv('POC_APP_ROOT'), '/');
$baseUrl = rtrim((string)getenv('POC_BASE_URL'), '/');
$sourceMarker = (string)getenv('POC_MARKER');
if (!in_array($dialect, ['mysql', 'dm'], true) || $root === '' || $baseUrl === ''
    || !preg_match('/^CDXPAY[A-F0-9]{8}$/', $sourceMarker)) exit(2);

$connect = static function (bool $ai) use ($dialect): PDO {
    $prefix = $ai ? 'POC_AI_' : 'POC_';
    $host = (string)getenv($prefix . 'HOST');
    $port = (int)getenv($prefix . 'PORT');
    $schema = (string)getenv($prefix . 'SCHEMA');
    $user = (string)getenv($prefix . 'USER');
    $password = (string)getenv($prefix . 'PASSWORD');
    if ($host === '' || $port <= 0 || $schema === '' || $user === '' || $password === '') exit(2);
    $dsn = $dialect === 'dm' ? "dm:host=$host;port=$port;schema=$schema"
        : "mysql:host=$host;port=$port;dbname=$schema;charset=utf8mb4";
    return new PDO($dsn, $user, $password, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
};
$store = $connect(false);
$ai = $connect(true);
$table = static function (string $name, bool $aiTable = false) use ($dialect): string {
    $prefix = $aiTable ? 'YOSHOP_' : 'YDY_';
    return $dialect === 'dm' ? $prefix . strtoupper($name)
        : strtolower($prefix . $name);
};
$cents = static function ($value): int {
    $value = (string)$value;
    if (!preg_match('/^(-?)([0-9]+)(?:\.([0-9]{1,2}))?$/D', $value, $parts)) {
        throw new RuntimeException('Unexpected DECIMAL money format');
    }
    return ($parts[1] === '-' ? -1 : 1)
        * ((int)$parts[2] * 100 + (int)str_pad($parts[3] ?? '', 2, '0'));
};

$orderStmt = $store->prepare('SELECT ID,USER_ID,RESTAURANT_ID FROM ' . $table('meal_order')
    . ' WHERE MSGID=?');
$orderStmt->execute([$sourceMarker]);
$sourceOrder = array_change_key_case($orderStmt->fetch(PDO::FETCH_ASSOC) ?: [], CASE_LOWER);
$userStmt = $store->prepare('SELECT ID,STAFF_UUID,AI_USER_ID FROM ' . $table('user')
    . ' WHERE ACCOUNT=? AND IS_DELETE=0');
$userStmt->execute([$sourceMarker]);
$sourceUser = array_change_key_case($userStmt->fetch(PDO::FETCH_ASSOC) ?: [], CASE_LOWER);
if (!$sourceOrder || !$sourceUser || (int)$sourceOrder['user_id'] !== (int)$sourceUser['id']) {
    throw new RuntimeException('Previously verified synthetic test user missing');
}
$aiUserId = (int)$sourceUser['ai_user_id'];
$balanceStmt = $ai->prepare('SELECT BALANCE FROM ' . $table('user', true)
    . ' WHERE USER_ID=? AND STORE_ID=10001');
$balanceStmt->execute([$aiUserId]);
$balanceBefore = $cents($balanceStmt->fetchColumn());
if ($balanceBefore < 1) throw new RuntimeException('Synthetic account balance too low');

$equipmentSql = 'SELECT E.ID,E.CODE,E.RESTAURANT_ID FROM ' . $table('equipment') . ' E'
    . ' JOIN ' . $table('restaurants') . ' R ON E.RESTAURANT_ID=R.ID'
    . " WHERE E.TYPE=9 AND E.DEL_FLAG=0 AND E.CODE<>'' AND E.RESTAURANT_ID>0"
    . ' AND R.STATUS=1 AND R.IS_DELETE=2 ORDER BY E.ID '
    . ($dialect === 'dm' ? 'FETCH FIRST 1 ROWS ONLY' : 'LIMIT 1');
$equipment = array_change_key_case($store->query($equipmentSql)->fetch(PDO::FETCH_ASSOC) ?: [], CASE_LOWER);
if (!$equipment) throw new RuntimeException('Real consume-equipment fixture missing');

$boundary = json_decode((string)file_get_contents($baseUrl . '/__poc_boundary'), true);
if (empty($boundary['external_stubs']) || empty($boundary['redis_isolated'])
    || empty($boundary['real_queue'])) {
    throw new RuntimeException('Isolated real queue boundary unavailable');
}
if (getenv('POC_DRY_RUN') === '1') {
    echo json_encode(['status' => 'preflight_passed', 'dialect' => $dialect,
        'synthetic_user_present' => true, 'real_consume_equipment_present' => true,
        'balance_has_one_cent' => true, 'external_stubs_and_queue_isolated' => true]) . PHP_EOL;
    exit;
}
if (getenv('POC_AUTH_ONLY') === '1') {
    $unauthorizedId = 'CDXNOAUTH' . strtoupper(bin2hex(random_bytes(4)));
    $form = [
        'card_id' => 'CDX-NONEXIST', 'equipment_code' => (string)$equipment['code'],
        'money' => '0.01', 'create_time' => date('Y-m-d H:i:s'),
        'msgid' => $unauthorizedId, 'order_type' => 1,
    ];
    $context = stream_context_create(['http' => [
        'method' => 'POST',
        'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
        'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
        'ignore_errors' => true, 'timeout' => 15,
    ]]);
    $body = file_get_contents($baseUrl . '/api/consume/orderPay', false, $context);
    $response = is_string($body) ? json_decode($body, true) : null;
    $orderCount = $store->prepare('SELECT COUNT(*) FROM ' . $table('meal_order') . ' WHERE MSGID=?');
    $orderCount->execute([$unauthorizedId]);
    $workCount = $store->prepare('SELECT COUNT(*) FROM ' . $table('consume_order_work') . ' WHERE MSGID=?');
    $workCount->execute([$unauthorizedId]);
    if (!is_array($response) || (int)($response['code'] ?? -1) !== 1
        || (string)($response['message'] ?? '') !== '接口认证失败'
        || !array_key_exists('data', $response) || $response['data'] !== []
        || strpos((string)($http_response_header[0] ?? ''), '200') === false
        || (int)$orderCount->fetchColumn() !== 0
        || (int)$workCount->fetchColumn() !== 0) {
        throw new RuntimeException('Unauthenticated device consume was not safely rejected');
    }
    echo json_encode(['status' => 'auth_rejected', 'dialect' => $dialect,
        'business_code_type' => gettype($response['code']),
        'shape_sha256' => pocJsonContractSignature($body)['sha256'],
        'store_orders' => 0, 'queue_work' => 0]) . PHP_EOL;
    exit;
}

$marker = 'CDXCONS' . strtoupper(bin2hex(random_bytes(5)));
$cardId = 'CDXDEVCARD' . strtoupper(bin2hex(random_bytes(6)));
$authAccount = 'CDXDEVAUTH' . strtoupper(bin2hex(random_bytes(5)));
$authPassword = strtoupper(bin2hex(random_bytes(8)));
$authKey = strtoupper(bin2hex(random_bytes(8)));
$forcedOrderNo = date('ymdHi') . sprintf('%05d', random_int(1, 99999));
$cardInserted = false;
$authInserted = false;
try {
    $insertCard = $store->prepare('INSERT INTO ' . $table('staff_card')
        . ' (STORE_ID,STAFF_UUID,CARD_ID,CARD_STATUS,IS_DELETE) VALUES (10001,?,?,1,0)');
    $insertCard->execute([(string)$sourceUser['staff_uuid'], $cardId]);
    $cardInserted = true;
    $keyColumn = $dialect === 'dm' ? '"KEY"' : '`key`';
    $insertAuth = $store->prepare('INSERT INTO ' . $table('api_auth')
        . ' (ACCOUNT,PWD,' . $keyColumn . ',TYPE,RESTAURANT_ID,STATUS,START_TIME,END_TIME)'
        . ' VALUES (?,?,?,1,?,1,?,?)');
    $insertAuth->execute([$authAccount, $authPassword, $authKey,
        (int)$equipment['restaurant_id'], date('Y-m-d H:i:s', time() - 3600),
        date('Y-m-d H:i:s', time() + 3600)]);
    $authInserted = true;

    $payload = [
        'staff_uuid' => '', 'paycode' => '', 'card_id' => $cardId,
        'equipment_code' => (string)$equipment['code'], 'money' => '0.01',
        'create_time' => date('Y-m-d H:i:s'), 'msgid' => $marker,
        'order_type' => 1,
    ];
    $responseContracts = [];
    $request = static function (array $form) use ($baseUrl, $authAccount, $authPassword,
        $authKey, $forcedOrderNo, &$responseContracts): array {
        $context = stream_context_create(['http' => [
            'method' => 'POST',
            'header' => "Content-Type: application/x-www-form-urlencoded\r\n"
                . 'account: ' . $authAccount . "\r\n"
                . 'pwd: ' . $authPassword . "\r\n"
                . 'key: ' . $authKey . "\r\n"
                . 'X-Poc-Order-No: ' . $forcedOrderNo . "\r\n",
            'content' => http_build_query($form, '', '&', PHP_QUERY_RFC3986),
            'ignore_errors' => true, 'timeout' => 30,
        ]]);
        $body = file_get_contents($baseUrl . '/api/consume/orderPay', false, $context);
        $response = is_string($body) ? json_decode($body, true) : null;
        if (!is_array($response)) throw new RuntimeException('Device consume response not JSON');
        $status = (string)($http_response_header[0] ?? '');
        $contentType = '';
        foreach ($http_response_header as $header) {
            if (stripos($header, 'Content-Type:') === 0) {
                $contentType = trim(substr($header, strlen('Content-Type:')));
            }
        }
        $responseContracts[] = [
            'http_200' => strpos($status, '200') !== false,
            'content_type' => $contentType,
            'shape_sha256' => pocJsonContractSignature($body)['sha256'],
            'business_code_type' => gettype($response['code'] ?? null),
        ];
        return $response;
    };
    $first = $request($payload);
    if ((int)($first['code'] ?? -1) !== 0) {
        throw new RuntimeException('Device consume HTTP failed: code=' . (string)($first['code'] ?? 'missing')
            . ' message=' . mb_substr((string)($first['message'] ?? ''), 0, 80));
    }
    echo json_encode(['dialect' => $dialect, 'status' => 'accepted', 'marker' => $marker]) . PHP_EOL;
    $inFlightDuplicate = $request($payload);
    if ((int)($inFlightDuplicate['code'] ?? -1) !== 1
        || (string)($inFlightDuplicate['message'] ?? '') !== '支付中，请勿重复支付') {
        throw new RuntimeException('Immediate duplicate did not retain the original in-flight contract');
    }

    $worker = escapeshellarg(PHP_BINARY);
    if ($dialect === 'dm') {
        $worker .= ' -d extension=' . escapeshellarg((string)getenv('POC_DM_PHP_LIB'))
            . ' -d extension=' . escapeshellarg((string)getenv('POC_DM_PHP_PDO'));
    }
    $worker .= ' -d auto_prepend_file=' . escapeshellarg($root . '/external_stubs.php')
        . ' ' . escapeshellarg($root . '/think')
        . ' queue:work --queue=serve_meal_order_BUSINESS_VALUE --sleep=0 --tries=1 --no-interaction';
    putenv('POC_REAL_QUEUE=1');
    putenv('POC_EXTERNAL_STUBS=1');
    for ($job = 0; $job < 2; $job++) {
        $process = proc_open($worker, [1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes, $root);
        if (!is_resource($process)) throw new RuntimeException('Isolated queue worker did not start');
        $stdout = stream_get_contents($pipes[1]);
        $stderr = stream_get_contents($pipes[2]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        if (proc_close($process) !== 0 || strpos($stdout, 'Processed:') === false) {
            throw new RuntimeException('Isolated queue worker failed; stderr_present='
                . ($stderr !== '' ? 'yes' : 'no'));
        }
    }
    $paidOrder = $store->prepare('SELECT ID,ORDER_NO,USER_ID,PAY_PRICE,RESTAURANT_ID'
        . ' FROM ' . $table('meal_order') . ' WHERE MSGID=? AND SOURCE=2 AND IS_DELETE=0');
    $paidOrder->execute([$marker]);
    $rows = array_map('array_change_key_case', $paidOrder->fetchAll(PDO::FETCH_ASSOC));
    if (count($rows) !== 1 || (int)$rows[0]['user_id'] !== (int)$sourceUser['id']
        || (int)$rows[0]['restaurant_id'] !== (int)$equipment['restaurant_id']
        || $cents($rows[0]['pay_price']) !== 1) {
        throw new RuntimeException('Device consume order database readback mismatch');
    }
    $paidOrderId = (int)$rows[0]['id'];
    $paidOrderNo = (string)$rows[0]['order_no'];
    $settlement = $ai->prepare('SELECT COUNT(*) FROM ' . $table('account_settlement', true)
        . " WHERE BIZ_ORDER_NO=? AND USER_ID=? AND BIZ_TYPE='consume'");
    $settlement->execute([$paidOrderNo, $aiUserId]);
    $cashLog = $ai->prepare('SELECT COUNT(*) FROM ' . $table('user_balance_log', true)
        . ' WHERE ORDER_ID=? AND USER_ID=?');
    $cashLog->execute([$paidOrderId, $aiUserId]);
    $balanceStmt->execute([$aiUserId]);
    $balanceAfter = $cents($balanceStmt->fetchColumn());
    if ((int)$settlement->fetchColumn() !== 1 || (int)$cashLog->fetchColumn() !== 1
        || $balanceAfter !== $balanceBefore - 1) {
        throw new RuntimeException('Device consume AI settlement/ledger/balance mismatch');
    }
    $redis = new \Redis();
    $redis->connect((string)getenv('POC_REDIS_HOST'), (int)getenv('POC_REDIS_PORT'), 1.0);
    $guardTtl = (int)$redis->ttl('consume_orderpay_' . $marker);
    if ($guardTtl > 0) usleep(($guardTtl + 1) * 1000000);
    $settledRetry = $request($payload);
    if ((int)($settledRetry['code'] ?? -1) !== 0) {
        throw new RuntimeException('Settled duplicate was not returned as an idempotent success');
    }
    $paidOrder->execute([$marker]);
    $settlement->execute([$paidOrderNo, $aiUserId]);
    $cashLog->execute([$paidOrderId, $aiUserId]);
    $balanceStmt->execute([$aiUserId]);
    if (count($paidOrder->fetchAll(PDO::FETCH_ASSOC)) !== 1
        || (int)$settlement->fetchColumn() !== 1
        || (int)$cashLog->fetchColumn() !== 1
        || $cents($balanceStmt->fetchColumn()) !== $balanceAfter) {
        throw new RuntimeException('Device consume duplicate altered persisted state');
    }
    if (count($responseContracts) !== 3
        || !$responseContracts[0]['http_200'] || !$responseContracts[1]['http_200']
        || !$responseContracts[2]['http_200']
        || $responseContracts[0]['shape_sha256'] !== $responseContracts[2]['shape_sha256']) {
        throw new RuntimeException('Device consume HTTP response envelope mismatch');
    }
    foreach ($responseContracts as $contract) {
        if ($contract['content_type'] !== 'application/json; charset=utf-8'
            || $contract['business_code_type'] !== 'integer') {
            throw new RuntimeException('Device consume HTTP response type mismatch');
        }
    }
    echo json_encode(['dialect' => $dialect, 'status' => 'passed',
        'marker' => $marker, 'order_id' => $paidOrderId,
        'order_no' => $paidOrderNo, 'paid_cents' => 1,
        'real_worker_jobs' => 2, 'in_flight_duplicate_code' => $inFlightDuplicate['code'] ?? null,
        'settled_retry_code' => $settledRetry['code'] ?? null,
        'guard_ttl_before_settled_retry' => $guardTtl,
        'response_contracts' => $responseContracts,
        'store_order_ai_settlement_cash_log_verified' => true]) . PHP_EOL;
} finally {
    if ($authInserted) {
        $store->prepare('DELETE FROM ' . $table('api_auth') . ' WHERE ACCOUNT=?')
            ->execute([$authAccount]);
    }
    if ($cardInserted) {
        $store->prepare('DELETE FROM ' . $table('staff_card') . ' WHERE CARD_ID=?')
            ->execute([$cardId]);
    }
    $checkAuth = $store->prepare('SELECT COUNT(*) FROM ' . $table('api_auth') . ' WHERE ACCOUNT=?');
    $checkAuth->execute([$authAccount]);
    $checkCard = $store->prepare('SELECT COUNT(*) FROM ' . $table('staff_card') . ' WHERE CARD_ID=?');
    $checkCard->execute([$cardId]);
    if ((int)$checkAuth->fetchColumn() !== 0 || (int)$checkCard->fetchColumn() !== 0) {
        throw new RuntimeException('Synthetic device auth/card cleanup left data');
    }
}
