<?php
declare(strict_types=1);
// Own ledger fixtures only; never modifies any user balance or business order.
$lane = (string)getenv('PILOT_LANE');
$runtime = (string)getenv('PILOT_RUNTIME');
$base = (string)getenv('POC_BASE_URL');
$dm = getenv('POC_DIALECT') === 'dm';
if (!in_array($lane, ['B', 'C'], true) || $base !== 'http://127.0.0.1:' . ($dm ? 18252 : 18251)) exit(78);
$stage = 'boundary';
set_exception_handler(static function (Throwable $e) use (&$stage): void {
    echo json_encode(['status' => 'failed', 'stage' => $stage, 'class' => get_class($e),
        'line' => $e->getLine(), 'message_sha256' => hash('sha256', $e->getMessage())]), "\n";
    exit(2);
});
$boundary = json_decode((string)file_get_contents($base . '/__poc_boundary'), true);
if (empty($boundary['external_stubs'])) throw new RuntimeException('Boundary unavailable');
$root = (string)getenv('PILOT_TARGET_ROOT');
define('TP_ENV', 'poc'); define('BUSINESS', 'aizhct'); define('ENTERPRISE_NO', '10001');
$loader = require $root . '/vendor/autoload.php';
if (is_file(__DIR__ . '/overlay/user/GiftLog.php')) {
    $loader->addPsr4('app\\api\\model\\user\\', __DIR__ . '/overlay/user/', true);
}
$app = new \think\App($root);
$app->setEnvName('poc');
$app->setRuntimePath($runtime . '/runtime/');
$app->initialize();
$app->config->set(['default' => 'file', 'stores' => ['file' => [
    'type' => 'File', 'path' => $runtime . '/cache/', 'prefix' => '', 'expire' => 0,
]]], 'cache');
$dsn = $dm ? 'dm:host=127.0.0.1;port=5238;schema=AIZHCT_APP'
    : 'mysql:host=' . getenv('POC_AI_HOST') . ';port=' . getenv('POC_AI_PORT')
        . ';dbname=' . getenv('POC_AI_SCHEMA') . ';charset=utf8';
$connect = static function () use ($dsn): PDO {
    return new PDO($dsn, getenv('POC_AI_USER'), getenv('POC_AI_PASSWORD'), [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
};
$ai = $connect();
$table = static function (string $name) use ($dm): string { return $dm ? strtoupper('yoshop_' . $name) : 'yoshop_' . $name; };
$select = static function (PDO $db, string $sql, array $args = []): array {
    $q = $db->prepare($sql); $q->execute($args); $rows = $q->fetchAll(PDO::FETCH_ASSOC);
    foreach ($rows as &$row) foreach ($row as &$value) if (is_resource($value)) $value = stream_get_contents($value);
    return $rows;
};
$ownerNick = $dm ? 'CDXPARMOB9411C2CB' : 'CDXPARMOB426CA4B0';
$owners = $select($ai, 'SELECT * FROM ' . $table('user') . ' WHERE NICK_NAME=? AND STORE_ID=10001 AND IS_DELETE=0', [$ownerNick]);
if (count($owners) !== 1) throw new RuntimeException('Synthetic owner not unique');
$ownerId = (int)$owners[0]['user_id'];
$ownerSha = hash('sha256', serialize($owners));
$storeCfg = $app->config->get('database.connections.mysql_zhct');
$storeDsn = $dm ? 'dm:host=127.0.0.1;port=5237;schema=ZHCT_APP'
    : 'mysql:host=' . $storeCfg['hostname'] . ';port=' . $storeCfg['hostport'] . ';dbname=' . $storeCfg['database'] . ';charset=utf8';
$store = new PDO($storeDsn, $storeCfg['username'], $storeCfg['password'], [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER]);
$storeUser = $dm ? 'YDY_USER' : 'ydy_user';
$mapping = $select($store, 'SELECT * FROM ' . $storeUser . ' WHERE AI_USER_ID=? AND IS_DELETE=0', [$ownerId]);
if (count($mapping) !== 1) throw new RuntimeException('Synthetic source mapping not unique');
$mappingSha = hash('sha256', serialize($mapping));
$marker = 'CDXGW10' . strtoupper(bin2hex(random_bytes(6)));
$token = $marker . 'T' . strtoupper(bin2hex(random_bytes(16)));
$date = '2031-01-15 12:00:00';
$meta = $dm ? $select($ai, "SELECT DATA_TYPE FROM USER_TAB_COLUMNS WHERE TABLE_NAME='YOSHOP_USER_GIFT_LOG' AND COLUMN_NAME='CREATE_TIME'")
    : $select($ai, "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='yoshop_user_gift_log' AND COLUMN_NAME='create_time'");
$type = strtolower((string)($meta[0]['data_type'] ?? ''));
$stamp = preg_match('/^(int|bigint)/', $type) ? strtotime($date . ' Asia/Shanghai') : $date;
if (!preg_match('/^(int|bigint|datetime|timestamp)/', $type)) throw new RuntimeException('Unknown ledger time type');
$prior = $select($ai, 'SELECT LOG_ID FROM ' . $table('user_gift_log') . ' WHERE USER_ID=? AND CREATE_TIME=?', [$ownerId, $stamp]);
if ($prior) throw new RuntimeException('Future test slot not empty');
$values = ['0', '7', '9007199254740993', '9223372036854775807', '18446744073709551615'];
$rows = []; $cases = []; $responses = []; $cleaned = false;
try {
    $stage = 'own_fixture_insert';
    foreach ($values as $i => $value) {
        $describe = $dm ? '"DESCRIBE"' : '`describe`';
        $insert = $ai->prepare('INSERT INTO ' . $table('user_gift_log')
            . ' (USER_ID,SCENE,MONEY,BALANCE_BEFORE,BALANCE_AFTER,BIZ_TYPE,BIZ_ORDER_ID,BIZ_ORDER_NO,'
            . 'IDEMPOTENCY_KEY,' . $describe . ',REMARK,OPERATOR_ID,OPERATOR_NAME,STORE_ID,CREATE_TIME)'
            . ' VALUES (?,20,?,?,?,?,?,?,?,?,?,?,?,10001,?)');
        $insert->execute([$ownerId, '-1.23', '100.00', '98.77', 'consume', $value,
            'CDXGW10_ORDER_' . $i, $marker . ':' . $i, 'CODEX_WIDE_GIFT_TEST', 'CODEX_WIDE_GIFT_TEST',
            'CODEX_TEST', 'CODEX_TEST', $stamp]);
        $cast = $dm ? 'CAST(BIZ_ORDER_ID AS VARCHAR(40))' : 'CAST(BIZ_ORDER_ID AS CHAR(40))';
        $found = $select($ai, 'SELECT LOG_ID,' . $cast . ' AS EXACT_ID FROM ' . $table('user_gift_log')
            . ' WHERE USER_ID=? AND IDEMPOTENCY_KEY=?', [$ownerId, $marker . ':' . $i]);
        if (count($found) !== 1 || (string)$found[0]['exact_id'] !== $value) throw new RuntimeException('Own exact insert readback mismatch');
        $rows[$i] = ['log_id' => (int)$found[0]['log_id'], 'expected' => $value];
    }
    $model = \app\api\model\User::detail($ownerId);
    if (!$model || (int)$model['user_id'] !== $ownerId) throw new RuntimeException('Synthetic auth model unavailable');
    \think\facade\Cache::set($token, ['user' => $model, 'store_id' => 10001, 'is_login' => true,
        'password_version' => (int)$mapping[0]['password_version'], 'login_type' => 'sms'], 300);
    $request = static function (string $path, array $args, string $scenario) use ($base, $token): array {
        $raw = file_get_contents($base . $path . '?' . http_build_query(['storeId' => 10001] + $args), false,
            stream_context_create(['http' => ['timeout' => 15, 'ignore_errors' => true, 'follow_location' => 0,
                'header' => "Platform: POC\r\nAccess-Token: " . $token . "\r\nX-Pilot-Scenario: " . $scenario . "\r\n"]]));
        preg_match('/^HTTP\/\S+\s+(\d+)/', $http_response_header[0] ?? '', $m);
        return ['http_status' => (int)($m[1] ?? 0), 'body' => json_decode((string)$raw, true)];
    };
    $check = static function (array $item, int $i) use ($rows, $ownerId, $values): array {
        if (($item['log_id'] ?? null) !== $rows[$i]['log_id'] || ($item['user_id'] ?? null) !== $ownerId
            || ($item['biz_order_no'] ?? '') !== 'CDXGW10_ORDER_' . $i) throw new RuntimeException('HTTP row ownership mismatch');
        $actual = $item['biz_order_id'] ?? null;
        $expectedType = $i === 4 ? 'string' : 'integer';
        return ['expected' => $values[$i], 'actual_type' => gettype($actual), 'actual_text' => (string)$actual,
            'exact' => gettype($actual) === $expectedType && (string)$actual === $values[$i]];
    };
    $stage = 'http_details';
    foreach ($rows as $i => $row) {
        $response = $request('/api/gift.Log/detail', ['logId' => $row['log_id']], 'PILOT_AI_GIFT_WIDE_DETAIL_' . $i);
        if ($response['http_status'] !== 200 || ($response['body']['status'] ?? null) !== 200) throw new RuntimeException('Positive detail failed');
        $item =& $response['body']['data']['data'];
        $cases['detail_' . $i] = $check($item, $i);
        $item['log_id'] = $i + 1; $item['user_id'] = 1;
        unset($item); $responses['detail_' . $i] = $response;
    }
    $stage = 'http_list';
    $response = $request('/api/gift.Log/list', ['year' => 2031, 'month' => 1, 'scene' => 20], 'PILOT_AI_GIFT_WIDE_LIST');
    $list =& $response['body']['data']['list']['data'];
    if ($response['http_status'] !== 200 || ($response['body']['status'] ?? null) !== 200 || count($list ?? []) !== 5) {
        throw new RuntimeException('Positive list fixture count mismatch');
    }
    foreach ($list as &$item) {
        $i = array_search((int)($item['log_id'] ?? 0), array_column($rows, 'log_id'), true);
        if ($i === false) throw new RuntimeException('Unexpected list row');
        $cases['list_' . $i] = $check($item, $i);
        $item['log_id'] = $i + 1; $item['user_id'] = 1;
    }
    unset($item, $list); $responses['list'] = $response;
} finally {
    $stage = 'cleanup';
    \think\facade\Cache::delete($token);
    foreach ($rows as $i => $row) {
        $q = $ai->prepare('DELETE FROM ' . $table('user_gift_log') . ' WHERE LOG_ID=? AND USER_ID=? AND IDEMPOTENCY_KEY=?');
        $q->execute([$row['log_id'], $ownerId, $marker . ':' . $i]);
        if ($q->rowCount() !== 1) throw new RuntimeException('Owned log cleanup count mismatch');
    }
    $fresh = $connect();
    $left = $select($fresh, 'SELECT LOG_ID FROM ' . $table('user_gift_log') . ' WHERE IDEMPOTENCY_KEY LIKE ?', [$marker . '%']);
    $unchangedOwner = $ownerSha === hash('sha256', serialize($select($fresh, 'SELECT * FROM ' . $table('user')
        . ' WHERE NICK_NAME=? AND STORE_ID=10001 AND IS_DELETE=0', [$ownerNick])));
    $unchangedMapping = $mappingSha === hash('sha256', serialize($select($store, 'SELECT * FROM ' . $storeUser
        . ' WHERE AI_USER_ID=? AND IS_DELETE=0', [$ownerId])));
    $cleaned = !$left && $unchangedOwner && $unchangedMapping && !\think\facade\Cache::has($token);
    if (!$cleaned) throw new RuntimeException('Independent cleanup/source audit failed');
}
$source = (new ReflectionClass(\app\api\model\user\GiftLog::class))->getFileName();
$result = ['lane' => $lane, 'cases' => $cases, 'case_count' => count($cases),
    'exact_failures' => count(array_filter($cases, static function (array $c): bool { return !$c['exact']; })),
    'ledger_rows_after' => 0, 'user_balances_and_mapping_unchanged' => $cleaned,
    'model_sha256' => hash_file('sha256', $source), 'responses' => $responses];
file_put_contents($runtime . '/gift_result.json', json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
chmod($runtime . '/gift_result.json', 0600);
echo json_encode(['lane' => $lane, 'cases' => count($cases), 'exact_failures' => $result['exact_failures'],
    'cleaned' => $cleaned, 'result_file' => $runtime . '/gift_result.json']), "\n";
exit($result['exact_failures'] === 0 ? 0 : 1);
