<?php

declare(strict_types=1);

// Read-only metadata and marker check. All other modes default-deny.
$lane = (string)getenv('CDX_GIFT_LANE');
if (($argv[1] ?? '') !== '--preflight' || !in_array($lane, ['A', 'B', 'C'], true)) {
    echo json_encode(['status' => 'default_deny', 'http' => 0, 'dml' => 0]), PHP_EOL;
    exit(78);
}
$base = $lane === 'C'
    ? '/tmp/zhct-ai-read2-VgKO4CDY/read2_schema_preflight.php'
    : __DIR__ . '/../parallel-ai-next-read-safe4/read2_schema_preflight.php';
if (!is_file($base) || is_link($base)
    || hash_file('sha256', $base)
        !== '8641ee015ad537d300de9c00f2c882e654b739d7c2194dd13ef4635ca9df1af0') {
    throw new RuntimeException('Approved identity/schema preflight drift');
}
putenv('CDX_READ2_TARGET=' . $lane);
putenv('CDX_READ2_SCHEMA_PREFLIGHT=1');
require $base; // Defines $ai, $store, $dialect, $config after identity verification.
$giftStage = 'gift_source';
$giftMissingColumn = '';
set_exception_handler(static function (Throwable $error)
    use (&$giftStage, &$giftMissingColumn): void {
    echo json_encode(['status' => 'gift_preflight_error', 'stage' => $giftStage,
        'error_class' => get_class($error), 'sql_code' => (string)$error->getCode(),
        'reason_sha' => hash('sha256', $error->getMessage()),
        'missing_column' => $giftMissingColumn,
        'business_http' => 0, 'dml' => 0], JSON_UNESCAPED_SLASHES), PHP_EOL;
    exit(2);
});
$ai->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$store->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER);
$runtimeRoot = ['A' => '/private/tmp/zhct-http-ai-macos.zjaU8h',
    'B' => '/private/tmp/zhct-http-ai-macos.x2CCZX',
    'C' => '/tmp/zhct-ai-http-20260929.3XDj3w'][$lane];
$sourceRoot = ['A' => '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/baseline-a-ai-api-V4.0.40',
    'B' => '/Users/wiscourper/Documents/workspace/local/dm8-full-acceptance.fF5yTy/ai_api',
    'C' => '/tmp/zhct-dm-poc-20260928.fcpiiN/ai_api'][$lane];
$files = [
    'app/api/controller/gift/Log.php'
        => '9d70fbc0555f77697065ddb6568cf85cdc9cfdd7b6c69589e4fb617cdf592c4d',
    'app/api/service/gift/Log.php'
        => '03c3af93dc4a08c0932ee573112dfe5ac9ad7e225c902ca18f0cb9de31df4681',
];
foreach ($files as $relative => $sha) {
    foreach (array_unique([$runtimeRoot, $sourceRoot]) as $root) {
        $file = $root . '/' . $relative;
        if (!is_file($file) || is_link($file)
            || hash_file('sha256', $file) !== $sha) {
            throw new RuntimeException('Gift route/model runtime source drift');
        }
    }
}
$modelRelative = 'app/api/model/user/GiftLog.php';
$expectedModelSha = $lane === 'A'
    ? 'cbe789c4d5d3f15ce99b3ac8a2ace58af9935d132edf70129f83cb7eb6ba2782'
    : '0e55edc5a3ea0a98eae2dee78500d3d2ba177a6ea059bdee077908d687862035';
$modelRoot = $sourceRoot;
$modelPath = $modelRoot . '/' . $modelRelative;
if (!is_file($modelPath) || is_link($modelPath)
    || hash_file('sha256', $modelPath) !== $expectedModelSha) {
    throw new RuntimeException('Gift actual Composer model source drift');
}
if ($lane === 'C') {
    $unusedShadow = $runtimeRoot . '/' . $modelRelative;
    if (!is_file($unusedShadow) || is_link($unusedShadow)
        || hash_file('sha256', $unusedShadow)
            !== 'cbe789c4d5d3f15ce99b3ac8a2ace58af9935d132edf70129f83cb7eb6ba2782') {
        throw new RuntimeException('Gift unused HTTP shadow drift');
    }
}
require_once $runtimeRoot . '/vendor/autoload.php';
if ((new ReflectionClass(\app\api\model\user\GiftLog::class))->getFileName()
    !== $modelPath) {
    throw new RuntimeException('Composer loaded unexpected GiftLog path');
}
$giftStage = 'gift_helper';
if ($lane !== 'A') {
    $helper = $sourceRoot . '/app/common/service/FixedScaleDecimalResponse.php';
    if (!is_file($helper) || is_link($helper)
        || hash_file('sha256', $helper)
            !== 'c3bce04654a313e2918667a8a1019eb7b4c45b98edf9a5218d3e8c40b8d333cc') {
        throw new RuntimeException('Fixed-scale decimal helper source drift');
    }
}
$giftStage = 'gift_columns';
$table = $dialect === 'dm' ? 'YOSHOP_USER_GIFT_LOG' : 'yoshop_user_gift_log';
$userTable = $dialect === 'dm' ? 'YOSHOP_USER' : 'yoshop_user';
$required = [
    'user_gift_log' => ['LOG_ID', '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'],
    'user' => ['USER_ID', 'STORE_ID', 'NICK_NAME', 'MOBILE',
        'GIFT_BALANCE', 'IS_DELETE', 'CREATE_TIME'],
];
$types = [];
$optionalMissing = [];
foreach ($required as $suffix => $columns) {
    $actualTable = $dialect === 'dm' ? 'YOSHOP_' . strtoupper($suffix)
        : 'yoshop_' . $suffix;
    $stmt = $dialect === 'dm'
        ? $ai->prepare('SELECT COLUMN_NAME,DATA_TYPE,DATA_SCALE,NULLABLE'
            . ' FROM USER_TAB_COLUMNS WHERE TABLE_NAME=?')
        : $ai->prepare('SELECT COLUMN_NAME,DATA_TYPE,NUMERIC_SCALE,IS_NULLABLE'
            . ' FROM INFORMATION_SCHEMA.COLUMNS'
            . ' WHERE TABLE_SCHEMA=? AND TABLE_NAME=?');
    $stmt->execute($dialect === 'dm' ? [$actualTable]
        : [$config['POC_AI_SCHEMA'], $actualTable]);
    $meta = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $row = array_change_key_case($row, CASE_LOWER);
        $meta[strtoupper((string)$row['column_name'])] = $row;
    }
    foreach ($columns as $name) {
        if (!isset($meta[$name])) {
            $giftMissingColumn = $actualTable . '.' . $name;
            throw new RuntimeException('Required gift table column absent');
        }
    }
    if ($suffix === 'user_gift_log') {
        foreach (['IS_DELETE', 'CREATE_BY', 'UPDATE_TIME'] as $optional) {
            if (!isset($meta[$optional])) {
                $optionalMissing[] = $actualTable . '.' . $optional;
            }
        }
    } elseif ($suffix === 'user' && !isset($meta['PASSWORD_VERSION'])) {
        $optionalMissing[] = $actualTable . '.PASSWORD_VERSION';
    }
    if ($suffix === 'user_gift_log') {
        foreach (['MONEY', 'BALANCE_BEFORE', 'BALANCE_AFTER'] as $money) {
            $row = $meta[$money];
            $scale = $dialect === 'dm' ? $row['data_scale'] : $row['numeric_scale'];
            if ((int)$scale !== 2) throw new RuntimeException('Gift DECIMAL scale drift');
            $types[$money] = ['type' => strtoupper((string)$row['data_type']),
                'scale' => 2, 'nullable' => $dialect === 'dm'
                    ? (string)$row['nullable'] : (string)$row['is_nullable']];
        }
    }
}
$count = static function (PDO $pdo, string $sql, array $args = []): int {
    $stmt = $pdo->prepare($sql);
    $stmt->execute($args);
    return (int)$stmt->fetchColumn();
};
$identityColumns = [[$userTable, 'USER_ID'], [$table, 'LOG_ID']];
$autoIds = [];
foreach ($identityColumns as $item) {
    [$oneTable, $column] = $item;
    if ($dialect === 'dm') {
        $stmt = $ai->prepare('CALL SP_TABLEDEF(?, ?)');
        $stmt->execute(['AIZHCT_APP', $oneTable]);
        $ddl = $stmt->fetchColumn();
        if (is_resource($ddl)) $ddl = stream_get_contents($ddl);
        $auto = preg_match('/"' . $column . '"\s+[A-Z0-9]+\s+'
            . '(?:AUTO_INCREMENT|IDENTITY)/i', (string)$ddl) === 1;
    } else {
        $auto = $count($ai, 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS'
            . ' WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND COLUMN_NAME=?'
            . " AND EXTRA LIKE '%auto_increment%'",
            [(string)$config['POC_AI_SCHEMA'], $oneTable, strtolower($column)]) === 1;
    }
    $autoIds[$column] = $auto;
}
if ($dialect === 'dm') {
$giftStage = 'gift_constraints';
    $giftTriggers = $count($ai, 'SELECT COUNT(*) FROM USER_TRIGGERS WHERE TABLE_NAME=?',
        [$table]);
    $userTriggers = $count($ai, 'SELECT COUNT(*) FROM USER_TRIGGERS WHERE TABLE_NAME=?',
        [$userTable]);
    $giftFk = $count($ai, "SELECT COUNT(*) FROM USER_CONSTRAINTS"
        . " WHERE TABLE_NAME=? AND CONSTRAINT_TYPE='R'", [$table]);
    $userInboundFk = $count($ai, 'SELECT COUNT(*) FROM ALL_CONSTRAINTS c'
        . ' JOIN ALL_CONSTRAINTS p ON c.R_OWNER=p.OWNER'
        . ' AND c.R_CONSTRAINT_NAME=p.CONSTRAINT_NAME'
        . " WHERE c.CONSTRAINT_TYPE='R' AND p.OWNER='AIZHCT_APP'"
        . ' AND p.TABLE_NAME=?', [$userTable]);
    $unique = $count($ai, 'SELECT COUNT(*) FROM USER_CONSTRAINTS'
        . " WHERE TABLE_NAME=? AND CONSTRAINT_TYPE IN ('P','U')", [$table]);
} else {
$giftStage = 'gift_constraints';
    $schema = (string)$config['POC_AI_SCHEMA'];
    $giftTriggers = $count($ai, 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS'
        . ' WHERE TRIGGER_SCHEMA=? AND EVENT_OBJECT_TABLE=?', [$schema, $table]);
    $userTriggers = $count($ai, 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS'
        . ' WHERE TRIGGER_SCHEMA=? AND EVENT_OBJECT_TABLE=?', [$schema, $userTable]);
    $giftFk = $count($ai, 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE'
        . ' WHERE TABLE_SCHEMA=? AND TABLE_NAME=?'
        . ' AND REFERENCED_TABLE_NAME IS NOT NULL', [$schema, $table]);
    $userInboundFk = $count($ai, 'SELECT COUNT(*) FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE'
        . ' WHERE REFERENCED_TABLE_SCHEMA=? AND REFERENCED_TABLE_NAME=?',
        [$schema, $userTable]);
    $unique = $count($ai, 'SELECT COUNT(DISTINCT INDEX_NAME)'
        . ' FROM INFORMATION_SCHEMA.STATISTICS'
        . ' WHERE TABLE_SCHEMA=? AND TABLE_NAME=? AND NON_UNIQUE=0',
        [$schema, $table]);
}
$giftStage = 'gift_marker_absence';
$prefix = 'CDXGL5%';
$oldUsers = $count($ai, 'SELECT COUNT(*) FROM ' . $userTable
    . ' WHERE STORE_ID=10001 AND NICK_NAME LIKE ?', [$prefix]);
$oldLogs = $count($ai, 'SELECT COUNT(*) FROM ' . $table
    . ' WHERE STORE_ID=10001 AND IDEMPOTENCY_KEY LIKE ?', [$prefix]);
if ($oldUsers !== 0 || $oldLogs !== 0) {
    throw new RuntimeException('Prior gift synthetic marker residue');
}
$giftStage = 'owned_synthetic_users';
$ownedMarkers = $lane === 'C'
    ? ['owner' => 'CDXPARMOB9411C2CB', 'foreign' => 'CDXPAY661BEE6B']
    : ['owner' => 'CDXPARMOB426CA4B0', 'foreign' => 'CDXPAY112FC189'];
$ownerRows = [];
$ownedUserIds = [];
$ownedGiftCounts = [];
$candidateMonthCounts = [];
$storeTable = $dialect === 'dm' ? 'YDY_USER' : 'ydy_user';
foreach ($ownedMarkers as $role => $marker) {
    $stmt = $ai->prepare('SELECT USER_ID,STORE_ID,NICK_NAME,IS_DELETE FROM '
        . $userTable . ' WHERE NICK_NAME=? AND STORE_ID=10001 AND IS_DELETE=0');
    $stmt->execute([$marker]);
    $found = $stmt->fetchAll(PDO::FETCH_ASSOC);
    if (count($found) !== 1) throw new RuntimeException('Owned synthetic AI user not unique');
    $user = array_change_key_case($found[0], CASE_LOWER);
    $userId = (int)$user['user_id'];
    if ($userId <= 0 || (string)$user['nick_name'] !== $marker) {
        throw new RuntimeException('Owned synthetic AI user identity drift');
    }
    $stmt = $store->prepare('SELECT ID,PASSWORD_VERSION FROM ' . $storeTable
        . ' WHERE AI_USER_ID=? AND IS_DELETE=0');
    $stmt->execute([$userId]);
    $storeRows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    if (count($storeRows) !== 1) {
        throw new RuntimeException('Owned synthetic Store user mapping not unique');
    }
    $ownerRows[$role] = ['user_id_sha12' => substr(hash('sha256', (string)$userId), 0, 12),
        'store_mapping_count' => 1,
        'password_version_type' => gettype(array_change_key_case($storeRows[0], CASE_LOWER)['password_version'])];
    $ownedUserIds[$role] = $userId;
    $ownedGiftCounts[$role] = $count($ai, 'SELECT COUNT(*) FROM ' . $table
        . ' WHERE USER_ID=?', [$userId]);
    $candidateMonthCounts[$role] = $count($ai, 'SELECT COUNT(*) FROM ' . $table
        . ' WHERE USER_ID=? AND CREATE_TIME>=? AND CREATE_TIME<?',
        [$userId, '2026-09-01 00:00:00', '2026-10-01 00:00:00']);
}
echo json_encode(['status' => 'gift_metadata_preflight_pass', 'lane' => $lane,
    'dialect' => $dialect, 'ai_and_store_identity_locked' => true,
    'amount_columns' => $types,
    'optional_missing_columns' => $optionalMissing,
    'gift_unique_constraints' => $unique,
    'gift_fk' => $giftFk, 'user_inbound_fk' => $userInboundFk,
    'gift_triggers' => $giftTriggers, 'user_triggers' => $userTriggers,
    'identity_columns_auto' => $autoIds,
    'own_prefix_users' => 0, 'own_prefix_logs' => 0,
    'controlled_synthetic_users' => $ownerRows,
    'controlled_user_existing_gift_rows' => $ownedGiftCounts,
    'candidate_2026_09_existing_rows' => $candidateMonthCounts,
    'gift_model_fixed2_ready' => $lane !== 'A',
    'business_http' => 0, 'dml' => 0], JSON_UNESCAPED_SLASHES), PHP_EOL;
