<?php
declare(strict_types=1);

// Synthetic store-10001 cart acceptance. Only this run's exact rows are deleted.
$lane = (string)($argv[1] ?? '');
if (!in_array($lane, ['B', 'C'], true)) throw new RuntimeException('B/C required');
$pid = $lane === 'B' ? 13127 : 53772;
$root = $lane === 'B' ? '/private/tmp/zhct-http-ai-macos.x2CCZX'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$base = 'http://127.0.0.1:' . ($lane === 'B' ? 18101 : 18102);
$dialect = $lane === 'B' ? 'mysql' : 'dm';
$marker = 'CDXCART' . strtoupper(bin2hex(random_bytes(5)));
$token = $marker . 'TK' . strtoupper(bin2hex(random_bytes(12)));
$journal = sys_get_temp_dir() . '/cdx-cart-' . strtolower($lane) . '-' . strtolower($marker) . '.json';
$ids = ['user' => 0, 'goods' => 0, 'sku' => 0, 'cart' => 0];
$events = [];
$uncertain = false;

if ($lane === 'B') {
    $cfg = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW)['DATABASE'];
    $ps = (string)shell_exec('/bin/ps eww -p ' . $pid . ' -o command=');
    $owner = trim((string)shell_exec('/usr/sbin/lsof -nP -t -iTCP:18101 -sTCP:LISTEN 2>/dev/null'));
    if (!is_array($cfg) || strtolower((string)$cfg['TYPE']) !== 'mysql'
        || (string)$cfg['HOSTPORT'] !== '3306' || $owner !== (string)$pid
        || strpos($ps, 'POC_DIALECT=mysql') === false
        || strpos($ps, 'POC_EXTERNAL_STUBS=1') === false) {
        throw new RuntimeException('B worker or local MySQL identity drift');
    }
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
        'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        if (strpos($ps, 'POC_AI_' . $to . '=' . $cfg[$from]) === false) {
            throw new RuntimeException('B worker differs from local DB config');
        }
    }
    foreach (['HOSTNAME' => 'HOST', 'HOSTPORT' => 'PORT', 'DATABASE' => 'SCHEMA',
        'USERNAME' => 'USER', 'PASSWORD' => 'PASSWORD'] as $from => $to) {
        putenv('POC_AI_' . $to . '=' . $cfg[$from]);
    }
    $dsn = 'mysql:host=' . $cfg['HOSTNAME'] . ';port=' . $cfg['HOSTPORT']
        . ';dbname=' . $cfg['DATABASE'] . ';charset=utf8mb4';
    $db = new PDO($dsn, $cfg['USERNAME'], $cfg['PASSWORD']);
    if (strpos((string)$db->query('SELECT VERSION()')->fetchColumn(), '5.6.') !== 0
        || strcasecmp((string)$db->query('SELECT DATABASE()')->fetchColumn(), $cfg['DATABASE']) !== 0) {
        throw new RuntimeException('Not the configured development MySQL 5.6');
    }
} else {
    $cmdline = str_replace("\0", ' ', (string)file_get_contents('/proc/' . $pid . '/cmdline'));
    $env = [];
    foreach (explode("\0", (string)file_get_contents('/proc/' . $pid . '/environ')) as $item) {
        $pair = explode('=', $item, 2);
        if (count($pair) === 2) $env[$pair[0]] = $pair[1];
    }
    if (strpos($cmdline, '-S 127.0.0.1:18102 -t ' . $root . '/public') === false
        || ($env['POC_DIALECT'] ?? '') !== 'dm'
        || ($env['POC_AI_HOST'] ?? '') !== '127.0.0.1'
        || ($env['POC_AI_PORT'] ?? '') !== '5238'
        || ($env['POC_AI_SCHEMA'] ?? '') !== 'AIZHCT_APP'
        || ($env['POC_EXTERNAL_STUBS'] ?? '') !== '1') {
        throw new RuntimeException('C isolated DM worker identity drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP',
        $env['POC_AI_USER'], $env['POC_AI_PASSWORD']);
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'AIZHCT_APP') {
        throw new RuntimeException('C DM schema drift');
    }
}
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
if (!is_file($root . '/external_stubs.php')) {
    throw new RuntimeException('External substitute source missing');
}
$t = static function (string $name) use ($lane): string {
    return 'yoshop_' . ($lane === 'B' ? $name : strtoupper($name));
};
$g = $t('goods'); $s = $t('goods_sku'); $u = $t('user'); $c = $t('cart');
$scalar = static function (string $sql, array $params = []) use ($db) {
    $q = $db->prepare($sql); $q->execute($params); return $q->fetchColumn();
};
$row = static function (string $sql, array $params = []) use ($db): ?array {
    $q = $db->prepare($sql); $q->execute($params); $v = $q->fetch(PDO::FETCH_ASSOC);
    return is_array($v) ? $v : null;
};
if ((int)$scalar("SELECT COUNT(*) FROM $g WHERE GOODS_NO=?", [$marker]) !== 0
    || (int)$scalar("SELECT COUNT(*) FROM $u WHERE NICK_NAME=?", [$marker]) !== 0) {
    throw new RuntimeException('Marker collision');
}
if ($lane === 'B') {
    $triggers = (int)$scalar("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA=DATABASE() AND EVENT_OBJECT_TABLE IN (?,?,?,?)", [$g,$s,$u,$c]);
} else {
    $triggers = (int)$scalar("SELECT COUNT(*) FROM USER_TRIGGERS WHERE TABLE_NAME IN (?,?,?,?)", [$g,$s,$u,$c]);
}
if ($triggers !== 0) throw new RuntimeException('Unexpected fixture-table triggers');
foreach ([$root . '/app/api/controller/Cart.php', $root . '/app/api/model/Cart.php',
    $root . '/app/api/service/Cart.php', $root . '/app/common/model/Goods.php'] as $file) {
    if (!is_file($file)) throw new RuntimeException('Worker source missing');
    $events['source_sha'][basename(dirname($file)) . '/' . basename($file)] = hash_file('sha256', $file);
}
$events['preflight'] = ['lane' => $lane, 'schema' => $lane === 'B' ? $cfg['DATABASE'] : 'AIZHCT_APP',
    'worker' => $base, 'external_stubs' => true, 'triggers' => 0,
    'owned_before' => ['goods' => 0, 'user' => 0]];

define('TP_ENV', 'poc'); define('BUSINESS', 'aizhct'); define('ENTERPRISE_NO', '10001');
require $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName('poc')->initialize();
$saveJournal = static function () use ($journal, &$ids, $marker): void {
    $data = json_encode(['marker' => $marker, 'ids' => $ids], JSON_UNESCAPED_UNICODE);
    if (file_put_contents($journal, $data, LOCK_EX) === false) throw new RuntimeException('Journal write failed');
    chmod($journal, 0600);
};
$request = static function (string $method, string $path, array $data = []) use ($base, $token, &$uncertain): array {
    $header = "platform: POC\r\nAccess-Token: $token\r\n";
    $body = '';
    if ($method === 'POST') {
        $header .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $body = http_build_query($data);
    }
    $context = stream_context_create(['http' => ['method' => $method, 'header' => $header,
        'content' => $body, 'ignore_errors' => true, 'timeout' => 20]]);
    $uncertain = true;
    $raw = @file_get_contents($base . $path . '?storeId=10001', false, $context);
    if ($raw === false) throw new RuntimeException('HTTP transport failed');
    $statusLine = (string)($http_response_header[0] ?? '');
    if (!preg_match('/^HTTP\/\S+\s+(\d{3})\b/', $statusLine, $matches)
        || (int)$matches[1] !== 200) throw new RuntimeException('HTTP status was not 200');
    $decoded = json_decode($raw, true);
    if (!is_array($decoded)) throw new RuntimeException('HTTP non-JSON');
    $decoded['__http_code'] = (int)$matches[1];
    $uncertain = false;
    return $decoded;
};
$summarize = static function (array $response): array {
    $list = $response['data']['list'] ?? null;
    $item = is_array($list) && count($list) ? reset($list) : null;
    $types = [];
    if (is_array($item)) foreach ($item as $key => $value) $types[$key] = gettype($value);
    $goods = is_array($item) ? ($item['goods'] ?? null) : null;
    $goodTypes = [];
    if (is_array($goods)) foreach ($goods as $key => $value) $goodTypes[$key] = gettype($value);
    $sku = is_array($goods) ? ($goods['skuInfo'] ?? null) : null;
    $skuTypes = [];
    if (is_array($sku)) foreach ($sku as $key => $value) $skuTypes[$key] = gettype($value);
    return ['http_code' => $response['__http_code'] ?? null,
        'status' => $response['status'] ?? null, 'message' => $response['message'] ?? null,
        'data_keys' => array_keys((array)($response['data'] ?? [])),
        'cartTotal_type' => gettype($response['data']['cartTotal'] ?? null),
        'cartTotal' => $response['data']['cartTotal'] ?? null,
        'list_count' => is_array($list) ? count($list) : null,
        'item_types' => $types, 'goods_types' => $goodTypes,
        'sku_types' => $skuTypes];
};

try {
    $db->beginTransaction();
    $now = time();
    $q = $db->prepare("INSERT INTO $g (GOODS_NAME,GOODS_NO,SPEC_TYPE,CONTENT,STOCK_TOTAL,STATUS,IS_DELETE,STORE_ID,CREATE_TIME,UPDATE_TIME) VALUES (?,?,10,?,100,10,0,10001,?,?)");
    $q->execute([$marker, $marker, '', $now, $now]);
    $ids['goods'] = (int)$db->lastInsertId();
    if ($ids['goods'] <= 0) throw new RuntimeException('Goods identity missing');
    $q = $db->prepare("INSERT INTO $s (GOODS_SKU_ID,GOODS_ID,GOODS_PRICE,LINE_PRICE,STOCK_NUM,GOODS_WEIGHT,GOODS_PROPS,SPEC_VALUE_IDS,STORE_ID,CREATE_TIME,UPDATE_TIME) VALUES ('0',?,1.25,1.50,100,0.5,'[]','[]',10001,?,?)");
    $q->execute([$ids['goods'], $now, $now]);
    $ids['sku'] = (int)$db->lastInsertId();
    if ($ids['sku'] <= 0) throw new RuntimeException('SKU identity missing');
    $q = $db->prepare("INSERT INTO $u (STORE_ID,MOBILE,NICK_NAME,BALANCE,GIFT_BALANCE,IS_DELETE,CREATE_TIME) VALUES (10001,'',?,'0.00','0.00',0,?)");
    $q->execute([$marker, $now]);
    $ids['user'] = (int)$db->lastInsertId();
    if ($ids['user'] <= 0) throw new RuntimeException('User identity missing');
    $db->commit();
    $saveJournal();
    if ((int)$scalar("SELECT COUNT(*) FROM $g WHERE GOODS_ID=? AND GOODS_NO=? AND STORE_ID=10001", [$ids['goods'], $marker]) !== 1
        || (int)$scalar("SELECT COUNT(*) FROM $s WHERE ID=? AND GOODS_ID=? AND GOODS_SKU_ID='0'", [$ids['sku'], $ids['goods']]) !== 1
        || (int)$scalar("SELECT COUNT(*) FROM $u WHERE USER_ID=? AND NICK_NAME=? AND STORE_ID=10001", [$ids['user'], $marker]) !== 1) {
        throw new RuntimeException('Fixture readback failed');
    }
    $events['fixture'] = ['ids' => $ids, 'goods_price' => '1.25', 'stock' => 100];
    if (!\think\facade\Cache::set($token, ['user' => ['user_id' => $ids['user']],
        'password_version' => 0, 'login_type' => 'thirdparty'], 300)) {
        throw new RuntimeException('Token cache failed');
    }
    $total0 = $request('GET', '/api/Cart/total');
    $events['total_initial'] = $summarize($total0);
    if (($total0['status'] ?? null) !== 200 || ($total0['data']['cartTotal'] ?? null) !== 0) {
        throw new RuntimeException('Initial cart total mismatch');
    }
    $add = $request('POST', '/api/Cart/add', ['goodsId' => $ids['goods'], 'goodsSkuId' => '0', 'goodsNum' => 2]);
    $events['add'] = $summarize($add);
    if (($add['status'] ?? null) !== 200 || ($add['data']['cartTotal'] ?? null) !== 2) {
        throw new RuntimeException('Cart add status/total mismatch');
    }
    $cart = $row("SELECT ID,GOODS_ID,GOODS_SKU_ID,GOODS_NUM,USER_ID,IS_DELETE,STORE_ID FROM $c WHERE USER_ID=? AND GOODS_ID=? AND GOODS_SKU_ID='0' AND IS_DELETE=0", [$ids['user'], $ids['goods']]);
    if ($cart) $ids['cart'] = (int)$cart['id'];
    $saveJournal();
    $events['db_after_add'] = $cart;
    if (!$cart || (int)$cart['goods_num'] !== 2 || (int)$cart['is_delete'] !== 0
        || (int)$cart['store_id'] !== 10001) throw new RuntimeException('Cart insert readback mismatch');
    $list = $request('GET', '/api/Cart/list');
    $events['list'] = $summarize($list);
    $item = $list['data']['list'][0] ?? null;
    if (($list['status'] ?? null) !== 200 || ($list['data']['cartTotal'] ?? null) !== 2
        || !is_array($item) || (int)($item['id'] ?? 0) !== $ids['cart']
        || (int)($item['goods_num'] ?? 0) !== 2
        || (int)($item['goods']['goods_id'] ?? 0) !== $ids['goods']
        || (string)($item['goods']['skuInfo']['goods_sku_id'] ?? '') !== '0') {
        throw new RuntimeException('Nonempty cart list source/shape mismatch');
    }
    $update = $request('POST', '/api/Cart/update', ['goodsId' => $ids['goods'], 'goodsSkuId' => '0', 'goodsNum' => 3]);
    $events['update'] = $summarize($update);
    $events['db_after_update'] = $row("SELECT ID,GOODS_NUM,IS_DELETE FROM $c WHERE ID=? AND USER_ID=?", [$ids['cart'], $ids['user']]);
    if (($update['status'] ?? null) !== 200 || ($update['data']['cartTotal'] ?? null) !== 3
        || (int)($events['db_after_update']['goods_num'] ?? 0) !== 3) {
        throw new RuntimeException('Cart update response/readback mismatch');
    }
    $total3 = $request('GET', '/api/Cart/total');
    $events['total_after_update'] = $summarize($total3);
    if ($ids['cart'] > 0) {
        $clear = $request('POST', '/api/Cart/clear', ['cartIds' => [$ids['cart']]]);
        $events['clear'] = $summarize($clear);
        $events['db_after_clear'] = $row("SELECT ID,GOODS_NUM,IS_DELETE FROM $c WHERE ID=? AND USER_ID=?", [$ids['cart'], $ids['user']]);
        if (($clear['status'] ?? null) !== 200 || ($clear['data']['cartTotal'] ?? null) !== 0
            || (int)($events['db_after_clear']['is_delete'] ?? 0) !== 1) {
            throw new RuntimeException('Cart targeted clear response/readback mismatch');
        }
    }
    $totalEnd = $request('GET', '/api/Cart/total');
    $events['total_after_clear'] = $summarize($totalEnd);
    if (($totalEnd['status'] ?? null) !== 200 || ($totalEnd['data']['cartTotal'] ?? null) !== 0) {
        throw new RuntimeException('Cleared cart total mismatch');
    }
} catch (Throwable $e) {
    $events['failure'] = get_class($e) . ': ' . $e->getMessage();
    if ($db->inTransaction()) $db->rollBack();
} finally {
    $events['cleanup'] = ['attempted' => false];
    if (!$uncertain && $ids['user'] > 0 && $ids['goods'] > 0 && $ids['sku'] > 0) {
        try {
            $owned = (int)$scalar("SELECT COUNT(*) FROM $g WHERE GOODS_ID=? AND GOODS_NO=?", [$ids['goods'], $marker]) === 1
                && (int)$scalar("SELECT COUNT(*) FROM $s WHERE ID=? AND GOODS_ID=?", [$ids['sku'], $ids['goods']]) === 1
                && (int)$scalar("SELECT COUNT(*) FROM $u WHERE USER_ID=? AND NICK_NAME=?", [$ids['user'], $marker]) === 1;
            $cartCount = (int)$scalar("SELECT COUNT(*) FROM $c WHERE USER_ID=? AND GOODS_ID=?", [$ids['user'], $ids['goods']]);
            if (!$owned || $cartCount > 1) throw new RuntimeException('Owned-row cleanup scope drift');
            $db->beginTransaction();
            if ($cartCount) {
                $q = $db->prepare("DELETE FROM $c WHERE ID=? AND USER_ID=? AND GOODS_ID=?");
                $q->execute([$ids['cart'], $ids['user'], $ids['goods']]);
                if ($q->rowCount() !== 1) throw new RuntimeException('Cart delete count drift');
            }
            foreach ([[$s, 'ID', $ids['sku']], [$g, 'GOODS_ID', $ids['goods']], [$u, 'USER_ID', $ids['user']]] as $target) {
                $q = $db->prepare('DELETE FROM ' . $target[0] . ' WHERE ' . $target[1] . '=?');
                $q->execute([$target[2]]);
                if ($q->rowCount() !== 1) throw new RuntimeException('Fixture delete count drift');
            }
            $db->commit();
            \think\facade\Cache::delete($token);
            $zero = [];
            foreach ([[$c, 'USER_ID', $ids['user']], [$s, 'ID', $ids['sku']],
                [$g, 'GOODS_ID', $ids['goods']], [$u, 'USER_ID', $ids['user']]] as $target) {
                $zero[$target[0]] = (int)$scalar('SELECT COUNT(*) FROM ' . $target[0]
                    . ' WHERE ' . $target[1] . '=?', [$target[2]]);
            }
            $events['cleanup'] = ['attempted' => true, 'zero' => $zero,
                'token_zero' => !\think\facade\Cache::has($token)];
            if (array_sum($zero) === 0 && $events['cleanup']['token_zero']) unlink($journal);
        } catch (Throwable $e) {
            if ($db->inTransaction()) $db->rollBack();
            $events['cleanup']['failure'] = get_class($e) . ': ' . $e->getMessage();
        }
    }
    echo json_encode($events, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_INVALID_UTF8_SUBSTITUTE) . PHP_EOL;
}
