<?php
declare(strict_types=1);

// One owned coupon + one isolated short-token actor. This file never calls checkout or coupon use.
$phase = (string)($argv[1] ?? '');
$lane = (string)($argv[2] ?? '');
$marker = (string)($argv[3] ?? '');
if (!in_array($phase, ['--audit', '--run'], true) || !in_array($lane, ['B', 'C'], true)
    || !preg_match('/^CDXMC[A-F0-9]{12}$/D', $marker)
    || ($phase === '--run' && getenv('CDX_MY_COUPON_RUN') !== 'ONE_OWNED_RECEIVE_V1')) {
    fwrite(STDERR, "DENIED business_http=0 business_dml=0\n");
    exit(78);
}
$root = $lane === 'B' ? '/private/tmp/zhct-http-ai-macos.x2CCZX'
    : '/tmp/zhct-ai-http-20260929.3XDj3w';
$port = $lane === 'B' ? 18101 : 18102;
if (!is_file($root . '/public/index.php') || !is_file($root . '/vendor/autoload.php')) {
    throw new RuntimeException('isolated AI worker root absent');
}
if ($lane === 'B') {
    $ini = parse_ini_file('/Users/wiscourper/Documents/workspace/zhct/zhctproject/ai_api/.env.local', true, INI_SCANNER_RAW);
    $aiCfg = is_array($ini) ? ($ini['DATABASE'] ?? null) : null;
    $storeCfg = is_array($ini) ? ($ini['DATABASE_ZHCT'] ?? null) : null;
    if (!is_array($aiCfg) || !is_array($storeCfg)
        || strtolower((string)($aiCfg['TYPE'] ?? '')) !== 'mysql'
        || (string)($aiCfg['HOSTPORT'] ?? '') !== '3306'
        || hash('sha256', (string)($aiCfg['HOSTNAME'] ?? '')) !== '47e8be48b0cfd56dcdb9d9d426e6637b245f340b4efad4cd2f084ec5851ecc8e'
        || hash('sha256', (string)($aiCfg['DATABASE'] ?? '')) !== 'cf7756b3bb52cf72f625689134015b877fdab3ad8eb3bb2ddd962bb35f6efd66') {
        throw new RuntimeException('B code-local development database identity drift');
    }
    foreach (['POC_DIALECT' => 'mysql',
        'POC_AI_HOST' => $aiCfg['HOSTNAME'], 'POC_AI_PORT' => $aiCfg['HOSTPORT'],
        'POC_AI_SCHEMA' => $aiCfg['DATABASE'], 'POC_AI_USER' => $aiCfg['USERNAME'],
        'POC_AI_PASSWORD' => $aiCfg['PASSWORD'],
        'POC_HOST' => $storeCfg['HOSTNAME'], 'POC_PORT' => $storeCfg['HOSTPORT'],
        'POC_SCHEMA' => $storeCfg['DATABASE'], 'POC_USER' => $storeCfg['USERNAME'],
        'POC_PASSWORD' => $storeCfg['PASSWORD']] as $name => $value) {
        putenv($name . '=' . $value);
    }
    $db = new PDO('mysql:host=' . $aiCfg['HOSTNAME'] . ';port=3306;dbname='
        . $aiCfg['DATABASE'] . ';charset=' . $aiCfg['CHARSET'],
        $aiCfg['USERNAME'], $aiCfg['PASSWORD'],
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
} else {
    if (getenv('POC_DIALECT') !== 'dm' || getenv('POC_AI_HOST') !== '127.0.0.1'
        || getenv('POC_AI_PORT') !== '5238' || getenv('POC_AI_SCHEMA') !== 'AIZHCT_APP'
        || getenv('POC_AI_USER') !== 'AIZHCT_APP'
        || getenv('POC_HOST') !== '127.0.0.1' || getenv('POC_PORT') !== '5237'
        || getenv('POC_SCHEMA') !== 'ZHCT_APP') {
        throw new RuntimeException('C isolated two-customer-database identity drift');
    }
    $db = new PDO('dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP',
        getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'),
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
    if ($db->query("SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL")->fetchColumn() !== 'AIZHCT_APP') {
        throw new RuntimeException('C effective schema drift');
    }
}
$fetch = static function (string $sql, array $args = []) use ($db): array {
    $stmt = $db->prepare($sql);
    $stmt->execute($args);
    return array_map(static function (array $row): array {
        return array_change_key_case($row, CASE_LOWER);
    }, $stmt->fetchAll(PDO::FETCH_ASSOC));
};
$ownCoupons = static function () use ($fetch, $marker): array {
    return $fetch('SELECT coupon_id,name,reduce_price,min_price,total_num,receive_num,status,is_delete,store_id'
        . ' FROM yoshop_coupon WHERE name=? AND store_id=10001', [$marker]);
};
$actorName = $lane === 'C' ? 'CDXPARMOB9411C2CB' : 'CDXPARMOB426CA4B0';
$actors = $fetch('SELECT user_id,nick_name FROM yoshop_user WHERE nick_name=? AND store_id=10001 AND is_delete=0', [$actorName]);
if (count($actors) !== 1 || (int)$actors[0]['user_id'] <= 0) {
    throw new RuntimeException('synthetic AI actor not uniquely available');
}
$userId = (int)$actors[0]['user_id'];
$ownClaims = static function () use ($fetch, $ownCoupons, $userId): array {
    $coupons = $ownCoupons();
    if (!$coupons) return [];
    if (count($coupons) !== 1) throw new RuntimeException('owned coupon not unique');
    return $fetch('SELECT user_coupon_id,coupon_id,user_id,reduce_price,min_price,is_use,is_expire,store_id'
        . ' FROM yoshop_user_coupon WHERE coupon_id=? AND user_id=? AND store_id=10001',
        [(int)$coupons[0]['coupon_id'], $userId]);
};
$token = 'CDXMCTK' . strtoupper(hash('sha256', $marker));
define('TP_ENV', 'poc');
define('BUSINESS', 'aizhct');
define('ENTERPRISE_NO', '10001');
require_once $root . '/vendor/autoload.php';
(new \think\App($root))->setEnvName('poc')->initialize();
if (strtolower((string)\think\facade\Config::get('cache.default')) !== 'file'
    || realpath((string)\think\facade\Config::get('cache.stores.file.path')) !== realpath($root . '/runtime/cache')) {
    throw new RuntimeException('isolated File cache drift');
}
$mapped = \think\facade\Db::connect('mysql_zhct')->name('user')
    ->where(['ai_user_id' => $userId, 'is_delete' => 0])->find();
if (empty($mapped) || !isset($mapped['password_version'])) {
    throw new RuntimeException('synthetic Store actor mapping/version absent');
}
$ormUser = \app\api\model\User::detail($userId);
if (empty($ormUser) || (int)$ormUser['user_id'] !== $userId) {
    throw new RuntimeException('isolated ORM AI actor drift');
}
if ($phase === '--audit') {
    $couponCount = count($ownCoupons());
    $claimCount = count($ownClaims());
    $tokenExists = \think\facade\Cache::has($token);
    echo json_encode(['lane' => $lane, 'phase' => 'audit', 'owned_coupon_count' => $couponCount,
        'owned_claim_count' => $claimCount, 'short_token_exists' => $tokenExists,
        'business_http' => 0, 'business_dml' => 0], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit($couponCount === 0 && $claimCount === 0 && !$tokenExists ? 0 : 2);
}
if ($ownCoupons() || \think\facade\Cache::has($token)) {
    throw new RuntimeException('owned coupon/token already exists');
}
$http = static function (string $method, string $path, array $params = []) use ($port, $token): array {
    if (!in_array($path, ['/api/MyCoupon/list', '/api/MyCoupon/receive'], true)
        || ($path === '/api/MyCoupon/receive' && $method !== 'POST')
        || ($path === '/api/MyCoupon/list' && $method !== 'GET')) {
        throw new RuntimeException('unsupported fixed coupon path/method');
    }
    $query = $method === 'GET' ? '?' . http_build_query(['storeId' => 10001] + $params) : '';
    $payload = $method === 'POST' ? http_build_query(['storeId' => 10001] + $params) : '';
    $headers = 'Access-Token: ' . $token . "\r\n"
        . "platform: MP-WEIXIN\r\nversion: 2.0.51\r\nenv-version: develop\r\n";
    if ($method === 'POST') $headers .= "Content-Type: application/x-www-form-urlencoded\r\n";
    $ctx = stream_context_create(['http' => ['method' => $method, 'header' => $headers,
        'content' => $payload, 'ignore_errors' => true, 'follow_location' => 0, 'timeout' => 20]]);
    $handle = @fopen('http://127.0.0.1:' . $port . $path . $query, 'rb', false, $ctx);
    if (!is_resource($handle)) throw new RuntimeException('isolated AI coupon HTTP unavailable');
    $transport = (string)($http_response_header[0] ?? '');
    $raw = stream_get_contents($handle, 1048577);
    fclose($handle);
    if (!preg_match('~^HTTP/\S+ 200(?: |$)~', $transport) || !is_string($raw) || strlen($raw) > 1048576) {
        throw new RuntimeException('coupon HTTP transport/cap failed at ' . $path);
    }
    $body = json_decode($raw, true);
    if (!is_array($body)) throw new RuntimeException('coupon non-JSON body at ' . $path);
    return $body;
};
$shape = static function ($value) use (&$shape) {
    if (!is_array($value)) return gettype($value);
    $result = [];
    foreach ($value as $key => $item) $result[(string)$key] = $shape($item);
    ksort($result);
    return $result;
};
$beforeClaimCount = (int)$db->query('SELECT COUNT(*) FROM yoshop_user_coupon WHERE store_id=10001')->fetchColumn();
$beforeCouponCount = (int)$db->query('SELECT COUNT(*) FROM yoshop_coupon WHERE store_id=10001')->fetchColumn();
$result = [];
$error = null;
try {
    if (!\think\facade\Cache::set($token, ['user' => $ormUser, 'store_id' => 10001,
        'is_login' => true, 'password_version' => (int)$mapped['password_version'],
        'login_type' => 'sms'], 300) || !\think\facade\Cache::has($token)) {
        throw new RuntimeException('isolated short token unavailable');
    }
    $now = time();
    $describeColumn = $lane === 'B' ? '`describe`' : '"DESCRIBE"';
    $insert = $db->prepare('INSERT INTO yoshop_coupon'
        . ' (name,coupon_type,reduce_price,discount,min_price,expire_type,expire_day,'
        . 'start_time,end_time,apply_range,apply_range_config,total_num,receive_num,'
        . $describeColumn . ',status,sort,is_delete,store_id,create_time,update_time)'
        . ' VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
    $insert->execute([$marker, 10, '12.34', 0, '56.78', 20, 0,
        $now - 86400, $now + 86400, 10, '[]', 10, 0,
        'CDX compatibility test only', 1, 0, 0, 10001, $now, $now]);
    $coupons = $ownCoupons();
    if (count($coupons) !== 1 || (int)$coupons[0]['receive_num'] !== 0
        || (int)$coupons[0]['status'] !== 1 || (int)$coupons[0]['is_delete'] !== 0) {
        throw new RuntimeException('owned coupon source before receive invalid');
    }
    $couponId = (int)$coupons[0]['coupon_id'];
    $receive = $http('POST', '/api/MyCoupon/receive', ['couponId' => $couponId]);
    if ((int)($receive['status'] ?? 0) !== 200) {
        throw new RuntimeException('MyCoupon/receive business failed: ' . (string)($receive['status'] ?? 'missing'));
    }
    $claims = $ownClaims();
    $coupons = $ownCoupons();
    if (count($claims) !== 1 || count($coupons) !== 1
        || (int)$claims[0]['user_id'] !== $userId
        || (int)$claims[0]['coupon_id'] !== $couponId
        || (int)$coupons[0]['receive_num'] !== 1
        || bccomp((string)$claims[0]['reduce_price'], '12.34', 2) !== 0
        || bccomp((string)$claims[0]['min_price'], '56.78', 2) !== 0) {
        throw new RuntimeException('received coupon original-table transaction readback invalid');
    }
    $list = $http('GET', '/api/MyCoupon/list', ['dataType' => 'all']);
    if ((int)($list['status'] ?? 0) !== 200 || !is_array($list['data']['list']['data'] ?? null)) {
        throw new RuntimeException('MyCoupon/list business/pagination failed');
    }
    $ownedHttp = array_values(array_filter($list['data']['list']['data'],
        static function ($row) use ($couponId): bool {
            return is_array($row) && (int)($row['coupon_id'] ?? 0) === $couponId;
        }));
    if (count($ownedHttp) !== 1 || (int)$ownedHttp[0]['user_coupon_id'] !== (int)$claims[0]['user_coupon_id']
        || bccomp((string)$ownedHttp[0]['reduce_price'], '12.34', 2) !== 0
        || bccomp((string)$ownedHttp[0]['min_price'], '56.78', 2) !== 0) {
        throw new RuntimeException('MyCoupon/list own claim/source mismatch');
    }
    $duplicate = $http('POST', '/api/MyCoupon/receive', ['couponId' => $couponId]);
    if ((int)($duplicate['status'] ?? 200) === 200
        || count($ownClaims()) !== 1 || (int)$ownCoupons()[0]['receive_num'] !== 1) {
        throw new RuntimeException('duplicate receive unexpectedly changed owned source');
    }
    $types = $shape($ownedHttp[0]);
    $result = ['lane' => $lane, 'owned_coupon_receipt_ok' => true,
        'receive_business_status' => $receive['status'],
        'receive_response_types' => $shape($receive),
        'duplicate_business_status' => $duplicate['status'] ?? null,
        'duplicate_response_types' => $shape($duplicate),
        'claim_count_after_first_and_repeat' => 1,
        'receive_num_after_first_and_repeat' => 1,
        'owned_list_row_types' => $types,
        'owned_list_row_type_sha256' => hash('sha256', json_encode($types)),
        'owned_list_reduce_price_type' => gettype($ownedHttp[0]['reduce_price']),
        'owned_list_min_price_type' => gettype($ownedHttp[0]['min_price']),
        'external_payment_oss_queue' => 0];
} catch (Throwable $caught) {
    $error = $caught;
} finally {
    if ($db->inTransaction()) $db->rollBack();
    foreach ($ownClaims() as $row) {
        $delete = $db->prepare('DELETE FROM yoshop_user_coupon'
            . ' WHERE user_coupon_id=? AND coupon_id=? AND user_id=? AND store_id=10001');
        $delete->execute([(int)$row['user_coupon_id'], (int)$row['coupon_id'], $userId]);
    }
    foreach ($ownCoupons() as $row) {
        $delete = $db->prepare('DELETE FROM yoshop_coupon WHERE coupon_id=? AND name=? AND store_id=10001');
        $delete->execute([(int)$row['coupon_id'], $marker]);
    }
    \think\facade\Cache::delete($token);
}
if ($ownCoupons() || $ownClaims() || \think\facade\Cache::has($token)) {
    throw new RuntimeException('owned coupon/claim/token cleanup failed');
}
if ((int)$db->query('SELECT COUNT(*) FROM yoshop_user_coupon WHERE store_id=10001')->fetchColumn() !== $beforeClaimCount
    || (int)$db->query('SELECT COUNT(*) FROM yoshop_coupon WHERE store_id=10001')->fetchColumn() !== $beforeCouponCount) {
    throw new RuntimeException('natural coupon/claim row counts drifted');
}
if ($error !== null) throw $error;
$result['owned_cleanup_independent_zero'] = true;
echo json_encode($result, JSON_UNESCAPED_SLASHES), PHP_EOL;
