<?php

declare(strict_types=1);

$root = (string)getenv('POC_TARGET_ROOT');
$baseUrl = rtrim((string)getenv('POC_BASE_URL'), '/');
$dialect = (string)getenv('POC_DIALECT');
if ($root === '' || $baseUrl === '' || !in_array($dialect, ['mysql', 'dm'], true)) {
    throw new RuntimeException('Isolated runtime, HTTP URL and dialect are required');
}
if ($dialect === 'mysql') {
    $local = parse_ini_file((string)getenv('POC_MYSQL_LOCAL_ENV'), true, INI_SCANNER_RAW);
    if (!is_array($local) || !isset($local['DATABASE'], $local['DATABASE_ZHCT'])) {
        throw new RuntimeException('Selected local development database config unavailable');
    }
    foreach (['DATABASE' => 'AI_', 'DATABASE_ZHCT' => ''] as $section => $prefix) {
        foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
            'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $source => $target) {
            putenv('POC_' . $prefix . $target . '=' . (string)$local[$section][$source]);
        }
    }
}
foreach (['HOST', 'PORT', 'SCHEMA', 'USER', 'PASSWORD',
    'AI_HOST', 'AI_PORT', 'AI_SCHEMA', 'AI_USER', 'AI_PASSWORD'] as $part) {
    if (getenv('POC_' . $part) === false) throw new RuntimeException('Database environment incomplete');
}
$boundary = json_decode((string)file_get_contents($baseUrl . '/__poc_boundary'), true);
if (empty($boundary['external_stubs'])) {
    throw new RuntimeException('Isolated external-call substitutes are not active');
}
define('TP_ENV', 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName('poc')->initialize();
set_exception_handler(static function (Throwable $error): void {
    fwrite(STDERR, 'MOBILE_MEAL_FAIL ' . get_class($error) . ': '
        . mb_substr($error->getMessage(), 0, 300) . PHP_EOL);
    exit(1);
});

$dsn = static function (string $prefix) use ($dialect): string {
    $host = getenv('POC_' . $prefix . 'HOST');
    $port = getenv('POC_' . $prefix . 'PORT');
    $schema = getenv('POC_' . $prefix . 'SCHEMA');
    return $dialect === 'dm' ? "dm:host=$host;port=$port;schema=$schema"
        : "mysql:host=$host;port=$port;dbname=$schema;charset=utf8mb4";
};
$ai = new PDO($dsn('AI_'), getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$store = new PDO($dsn(''), getenv('POC_USER'), getenv('POC_PASSWORD'),
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$t = static function (string $prefix, string $name) use ($dialect): string {
    $table = $prefix . $name;
    return $dialect === 'dm' ? strtoupper($table) : $table;
};
$aUser = $t('yoshop_', 'user');
$aCart = $t('yoshop_', 'meal_cart');
$aDish = $t('yoshop_', 'dishes');
$aSettlement = $t('yoshop_', 'account_settlement');
$aBalanceLog = $t('yoshop_', 'user_balance_log');
$sStaff = $t('ydy_', 'staff');
$sUser = $t('ydy_', 'user');
$sOrder = $t('ydy_', 'meal_order');
$sInitialMenu = $t('ydy_', 'initial_menu');
$sPickup = $t('ydy_', 'meal_order_pickup');
$sRestaurant = $t('ydy_', 'restaurants');
$sRecipes = $t('ydy_', 'recipes');
$sDishes = $t('ydy_', 'dishes');
$limit = $dialect === 'dm' ? 'FETCH FIRST 200 ROWS ONLY' : 'LIMIT 200';
$candidateSql = 'SELECT r.ID RESTAURANT_ID,x.MENU_DATE,x.MEAL_TIMES,'
    . 'd.AI_DISHES_ID,d.PRICE,d.ORDER_NUM,d.UUID DISH_UUID,r.BOOK_RULES,'
    . 'r.MORNING_START,r.MORNING_END,r.NOON_START,r.NOON_END,r.NIGHT_START,r.NIGHT_END'
    . " FROM $sRestaurant r JOIN $sRecipes x ON x.RESTAURANT_ID=r.ID"
    . " JOIN $sDishes d ON d.UUID=x.DISHES_UUID"
    . ' WHERE r.STALL_TYPE=2 AND r.STATUS=1 AND r.IS_DELETE=2'
    . ' AND x.MENU_DATE>CURRENT_DATE'
    . ' AND d.AI_DISHES_ID>0 AND d.IS_DELETE=0 AND d.PRICE>0 AND d.PRICE<=10'
    . ' AND d.ORDER_NUM<=1'
    . " ORDER BY x.MENU_DATE,x.ID $limit";
$aiDishCheck = $ai->prepare("SELECT COUNT(*) FROM $aDish WHERE ID=? AND IS_DELETE=0");
$candidate = [];
$todaySql = str_replace('x.MENU_DATE>CURRENT_DATE', 'x.MENU_DATE=CURRENT_DATE',
    str_replace('ORDER BY x.MENU_DATE,x.ID', 'ORDER BY x.MEAL_TIMES DESC,x.ID', $candidateSql));
foreach ([$candidateSql, $todaySql] as $pass => $sql) {
    foreach ($store->query($sql, PDO::FETCH_ASSOC) as $row) {
        $row = array_change_key_case($row);
        $rules = json_decode((string)$row['book_rules'], true);
        $rule3 = (int)($rules['rule'] ?? 0) === 3;
        if ($pass === 0 && $rule3) continue;
        if ($pass === 1) {
            if (!$rule3) continue;
            $window = [1 => ['morning_start', 'morning_end'],
                2 => ['noon_start', 'noon_end'], 3 => ['night_start', 'night_end']]
                [(int)$row['meal_times']] ?? null;
            $clock = date('H:i');
            if ($window === null || $clock < substr((string)$row[$window[0]], 0, 5)
                || $clock > substr((string)$row[$window[1]], 0, 5)) continue;
        }
        $aiDishCheck->execute([(int)$row['ai_dishes_id']]);
        if ((int)$aiDishCheck->fetchColumn() !== 1) continue;
        $candidate = $row;
        break 2;
    }
}
if ($candidate === []) throw new RuntimeException('No mapped future recipe suitable for isolated test');
$marker = 'CDXPARMOB' . strtoupper(bin2hex(random_bytes(4)));
$staffUuid = 'CDXPARMOB-' . strtoupper(bin2hex(random_bytes(10)));
$userUuid = 'CDXPARMOB-' . strtoupper(bin2hex(random_bytes(10)));
$token = $marker . 'TOKEN' . strtoupper(bin2hex(random_bytes(12)));
$aiId = 0;
$staffId = 0;
$storeUserId = 0;
$orderId = 0;
$cartId = 0;
$moneyCents = static function ($amount): int {
    $value = (string)$amount;
    if (!preg_match('/^(-?)(\d+)(?:\.(\d{1,2}))?$/', $value, $m)) {
        throw new RuntimeException('Unexpected money representation');
    }
    return ($m[1] === '-' ? -1 : 1) * ((int)$m[2] * 100
        + (int)str_pad($m[3] ?? '', 2, '0'));
};
$read = static function (PDO $pdo, string $sql, array $params = []): array {
    $q = $pdo->prepare($sql);
    $q->execute($params);
    return array_change_key_case((array)$q->fetch(PDO::FETCH_ASSOC));
};
$count = static function (PDO $pdo, string $sql, array $params = []): int {
    $q = $pdo->prepare($sql);
    $q->execute($params);
    return (int)$q->fetchColumn();
};
$request = static function (string $method, string $path, ?string $auth = null,
    array $body = []) use ($baseUrl): array {
    $headers = "platform: POC\r\nContent-Type: application/json\r\n";
    if ($auth !== null) $headers .= 'Access-Token: ' . $auth . "\r\n";
    $options = ['method' => $method, 'header' => $headers,
        'ignore_errors' => true, 'timeout' => 25];
    if ($method !== 'GET') $options['content'] = json_encode($body, JSON_UNESCAPED_UNICODE);
    $raw = file_get_contents($baseUrl . $path, false, stream_context_create(['http' => $options]));
    $statusLine = $http_response_header[0] ?? '';
    $json = is_string($raw) ? json_decode($raw, true) : null;
    if (!is_array($json)) throw new RuntimeException('Non-JSON HTTP response at ' . $path
        . ': ' . $statusLine);
    unset($json['trace_id']);
    return ['http' => (int)(preg_match('/HTTP\/\S+\s+(\d+)/', $statusLine, $m) ? $m[1] : 0),
        'body' => $json];
};
$ok = static function (array $result, string $step): array {
    $body = $result['body'];
    if ($result['http'] !== 200 || (int)($body['status'] ?? -1) !== 200) {
        throw new RuntimeException($step . ' failed: http=' . $result['http']
            . ', status=' . ($body['status'] ?? 'missing')
            . ', message=' . mb_substr((string)($body['message'] ?? ''), 0, 180));
    }
    return $body;
};
$assert = static function (bool $condition, string $message): void {
    if (!$condition) throw new RuntimeException($message);
};
try {
    $now = date('Y-m-d H:i:s');
    $insertAi = $ai->prepare('INSERT INTO ' . $aUser
        . ' (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME)'
        . ' VALUES (10001,?,?,?,?,0,?)');
    $insertAi->execute(['', $marker, '100.00', '0.00', time()]);
    $aiId = (int)$ai->lastInsertId();
    $insertStaff = $store->prepare('INSERT INTO ' . $sStaff
        . ' (UUID,STATION_UUID,NAME,BIRTHDAY,SPORT_UUID,SPORT_EVENT_UUID,IN_TIME,'
        . 'CREATE_TIME,CREATE_BY,PERSONNEL_UMBER,PERMANENT_ADDRESS,COACH_NAME,SPORT_YEAR,'
        . 'IS_SHOW,AI_USER_ID) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,1,?)');
    $insertStaff->execute([$staffUuid, 'CDXPARMOB-STATION', $marker, '1990-01-01',
        'CDXPARMOB-SPORT', '[]', date('Y-m-d'), $now, 'CODEX', $marker,
        'CODEX TEST', 'CODEX TEST', '0', $aiId]);
    $staffId = (int)$store->lastInsertId();
    $insertStoreUser = $store->prepare('INSERT INTO ' . $sUser
        . ' (UUID,STAFF_UUID,AI_USER_ID,ACCOUNT,PWD,SALT,NAME,CREATE_TIME,CREATE_BY,IS_DELETE)'
        . ' VALUES (?,?,?,?,?,?,?,?,?,0)');
    $insertStoreUser->execute([$userUuid, $staffUuid, $aiId, $marker,
        'DISABLED', strtoupper(bin2hex(random_bytes(8))), $marker, $now, 'CODEX']);
    $storeUserId = (int)$store->lastInsertId();
    $assert(min($aiId, $staffId, $storeUserId) > 0,
        'Synthetic linked identities missing IDs');
    \think\facade\Cache::set($token, ['user' => ['user_id' => $aiId],
        'password_version' => 0, 'login_type' => 'thirdparty'], 300);
    $base = '?storeId=10001';
    $unauth = $request('POST', '/api/mealCheckout/submit' . $base, null,
        ['restaurant_id' => (int)$candidate['restaurant_id']]);
    $assert((int)($unauth['body']['status'] ?? 0) === 401
        && $count($store, "SELECT COUNT(*) FROM $sOrder WHERE USER_ID=?", [$storeUserId]) === 0,
        'Unauthenticated submit accepted or wrote an order');
    $cart = $ok($request('POST', '/api/mealCart/add' . $base, $token, [
        'restaurant_id' => (int)$candidate['restaurant_id'],
        'meal_date' => substr((string)$candidate['menu_date'], 0, 10),
        'meal_times' => (int)$candidate['meal_times'],
        'ai_dishes_id' => (int)$candidate['ai_dishes_id'], 'quantity' => 1,
    ]), 'cart add');
    $cartRow = $read($ai, "SELECT ID,DISHES_ID,DISHES_NUM,IS_DELETE FROM $aCart WHERE USER_ID=?",
        [$aiId]);
    $cartId = (int)($cartRow['id'] ?? 0);
    $assert($cartId > 0 && (int)$cartRow['dishes_id'] === (int)$candidate['ai_dishes_id']
        && (int)$cartRow['dishes_num'] === 1 && (int)$cartRow['is_delete'] === 0,
        'Cart add original table readback mismatch');
    $submit = $ok($request('POST', '/api/mealCheckout/submit' . $base, $token, [
        'restaurant_id' => (int)$candidate['restaurant_id'],
        'meal_date' => substr((string)$candidate['menu_date'], 0, 10),
        'meal_times' => (int)$candidate['meal_times'], 'cartIds' => [$cartId],
        'restaurant_method' => 1, 'user_name' => $marker,
        'user_mobile' => '13900000000', 'remark' => $marker,
    ]), 'submit');
    $orderId = (int)($submit['data']['orderId'] ?? 0);
    $assert($orderId > 0 && (int)($submit['data']['isPay'] ?? -1) === 1,
        'Submit did not return payable order');
    $order = $read($store, 'SELECT ID,ORDER_NO,USER_ID,RESTAURANT_ID,MEAL_DATE,MEAL_TIMES,'
        . 'PAY_PRICE,PAY_STATUS,SOURCE,REMARK,ACCOUNT_SETTLEMENT_ID'
        . " FROM $sOrder WHERE ID=?", [$orderId]);
    $assert((int)$order['user_id'] === $storeUserId
        && (int)$order['restaurant_id'] === (int)$candidate['restaurant_id']
        && (int)$order['pay_status'] === 10 && (int)$order['source'] === 4
        && $order['remark'] === $marker && $moneyCents($order['pay_price']) > 0
        && $count($store, "SELECT COUNT(*) FROM $sInitialMenu WHERE MEAL_ORDER_ID=?", [$orderId]) >= 1,
        'Pending order or dish original table readback mismatch');
    $cartAfter = $read($ai, "SELECT IS_DELETE FROM $aCart WHERE ID=?", [$cartId]);
    $assert((int)$cartAfter['is_delete'] === 1, 'Submit did not clear cart');
    $orderInfo = $ok($request('GET', '/api/mealCashier/orderInfo' . $base
        . '&orderId=' . $orderId . '&client=H5', $token), 'cashier order info');
    $assert((int)($orderInfo['data']['order']['orderId'] ?? 0) === $orderId,
        'Cashier info order mismatch');
    $beforeBalance = $moneyCents($read($ai,
        "SELECT BALANCE FROM $aUser WHERE USER_ID=?", [$aiId])['balance']);
    $pay = $ok($request('POST', '/api/mealCashier/orderPay' . $base, $token,
        ['orderId' => $orderId, 'method' => 'balance', 'client' => 'H5']), 'balance payment');
    $paidOrder = $read($store, 'SELECT ORDER_NO,PAY_PRICE,PAY_STATUS,PAY_METHOD,CASH,'
        . 'SUBSIDY,GIFT,ACCOUNT_SETTLEMENT_ID FROM ' . $sOrder . ' WHERE ID=?', [$orderId]);
    $settlement = $read($ai, 'SELECT SETTLEMENT_ID,BIZ_ORDER_NO,AMOUNT,CASH_AMOUNT,'
        . 'SUBSIDY_AMOUNT,GIFT_AMOUNT,STATUS,USER_ID FROM ' . $aSettlement
        . " WHERE BIZ_ORDER_NO=? AND BIZ_TYPE='consume'", [$order['order_no']]);
    $afterBalance = $moneyCents($read($ai,
        "SELECT BALANCE FROM $aUser WHERE USER_ID=?", [$aiId])['balance']);
    $cashLogCount = $count($ai,
        "SELECT COUNT(*) FROM $aBalanceLog WHERE USER_ID=? AND ORDER_ID=?",
        [$aiId, $orderId]);
    $assert((int)$paidOrder['pay_status'] === 20 && $paidOrder['pay_method'] === 'balance'
        && (int)$settlement['status'] === 20 && (int)$settlement['user_id'] === $aiId
        && (int)$settlement['settlement_id'] === (int)$paidOrder['account_settlement_id']
        && $moneyCents($settlement['amount']) === $moneyCents($paidOrder['pay_price'])
        && $moneyCents($settlement['cash_amount']) === $moneyCents($paidOrder['pay_price'])
        && $beforeBalance - $afterBalance === $moneyCents($paidOrder['pay_price'])
        && $cashLogCount === 1,
        'Paid order, settlement, cash log or exact cents mismatch');
    $repeat = $request('POST', '/api/mealCashier/orderPay' . $base, $token,
        ['orderId' => $orderId, 'method' => 'balance', 'client' => 'H5']);
    $repeatSettlements = $count($ai,
        "SELECT COUNT(*) FROM $aSettlement WHERE BIZ_ORDER_NO=? AND BIZ_TYPE='consume'",
        [$order['order_no']]);
    $repeatBalance = $moneyCents($read($ai,
        "SELECT BALANCE FROM $aUser WHERE USER_ID=?", [$aiId])['balance']);
    $assert((int)($repeat['body']['status'] ?? 200) !== 200
        && $repeatSettlements === 1 && $repeatBalance === $afterBalance
        && $count($ai, "SELECT COUNT(*) FROM $aBalanceLog WHERE USER_ID=? AND ORDER_ID=?",
            [$aiId, $orderId]) === 1,
        'Repeated payment changed the settlement or balance');
    $orders = $ok($request('GET', '/api/mealOrder/list' . $base, $token), 'order list');
    $detail = $ok($request('GET', '/api/mealOrder/detail' . $base . '&orderId=' . $orderId,
        $token), 'order detail');
    $assert((int)($detail['data']['data']['id'] ?? 0) === $orderId,
        'Paid order detail mismatch');
    $found = false;
    foreach ((array)($orders['data']['list']['data'] ?? []) as $item) {
        if ((int)($item['id'] ?? 0) === $orderId) $found = true;
    }
    $assert($found, 'Paid order missing from user order list');
    $pickupCount = $count($store, "SELECT COUNT(*) FROM $sPickup WHERE MEAL_ORDER_ID=?",
        [$orderId]);
    echo json_encode([
        'dialect' => $dialect, 'status' => 'passed',
        'marker' => $marker, 'ai_user_id' => $aiId, 'store_staff_id' => $staffId,
        'store_user_id' => $storeUserId, 'order_id' => $orderId,
        'restaurant_id' => (int)$candidate['restaurant_id'],
        'meal_date' => substr((string)$candidate['menu_date'], 0, 10),
        'meal_times' => (int)$candidate['meal_times'],
        'pay_cents' => $moneyCents($paidOrder['pay_price']),
        'balance_before_cents' => $beforeBalance, 'balance_after_cents' => $afterBalance,
        'settlement_count' => $repeatSettlements, 'cash_log_count' => $cashLogCount,
        'pickup_count' => $pickupCount,
        'response_contract' => [
            'unauth_status' => $unauth['body']['status'],
            'submit_status' => $submit['status'], 'submit_keys' => array_keys($submit['data']),
            'order_info_status' => $orderInfo['status'],
            'order_info_keys' => array_keys($orderInfo['data']),
            'pay_status' => $pay['status'], 'pay_message' => $pay['message'],
            'pay_keys' => array_keys($pay['data']),
            'repeat_status' => $repeat['body']['status'],
            'repeat_message' => $repeat['body']['message'] ?? '',
            'order_list_status' => $orders['status'],
            'order_detail_status' => $detail['status'],
        ],
        'audit_rows_retained' => true,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
} finally {
    \think\facade\Cache::delete($token);
    if ($storeUserId > 0 && $orderId === 0) {
        $existing = $read($store, "SELECT ID FROM $sOrder WHERE USER_ID=? AND REMARK=?",
            [$storeUserId, $marker]);
        $orderId = (int)($existing['id'] ?? 0);
    }
    if ($orderId === 0) {
        if ($aiId > 0) {
            $ai->prepare("DELETE FROM $aCart WHERE USER_ID=?")->execute([$aiId]);
        }
        if ($storeUserId > 0) {
            $store->prepare("DELETE FROM $sUser WHERE ID=? AND ACCOUNT=?")
                ->execute([$storeUserId, $marker]);
        }
        if ($staffId > 0) {
            $store->prepare("DELETE FROM $sStaff WHERE ID=? AND NAME=?")
                ->execute([$staffId, $marker]);
        }
        if ($aiId > 0) {
            $ai->prepare("DELETE FROM $aUser WHERE USER_ID=? AND NICK_NAME=?")
                ->execute([$aiId, $marker]);
        }
    } else {
        fwrite(STDERR, 'Synthetic order/identity ledger retained: marker=' . $marker
            . ' ai_user_id=' . $aiId . ' store_user_id=' . $storeUserId
            . ' store_staff_id=' . $staffId . ' order_id=' . $orderId . PHP_EOL);
    }
}
