<?php
declare(strict_types=1);

// Nonproduction ONLY, user-authorized 2026-10-10 single-column alignment.
// Source: code-local MySQL zhct.ydy_meal_order BANK_USER_NAME VARCHAR(255)
// utf8mb4, NOT NULL, DEFAULT ''. Never execute the whole customer SQL.
$action = (string)(getenv('POC_SCHEMA_ACTION') ?: 'preflight');
$schema = strtoupper((string)getenv('POC_SCHEMA'));
if ($schema !== 'ZHCT_APP' || getenv('POC_DIALECT') !== 'dm'
    || getenv('POC_HOST') !== '127.0.0.1' || getenv('POC_PORT') !== '5237') {
    throw new RuntimeException('This repair is restricted to the approved DM test schema');
}
$pdo = new PDO('dm:host=' . getenv('POC_HOST') . ';port=' . getenv('POC_PORT')
    . ';schema=' . $schema, getenv('POC_USER'), getenv('POC_PASSWORD'), [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_CASE => PDO::CASE_LOWER,
    ]);
$table = 'YDY_MEAL_ORDER';
$definitions = ['BANK_USER_NAME' => "VARCHAR(255 CHAR) DEFAULT ''"];
$nullable = [];
$columns = static function (string $name) use ($pdo): array {
    $q = $pdo->prepare('SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH,CHAR_LENGTH,CHAR_USED,DATA_PRECISION,'
        . 'DATA_SCALE,NULLABLE,DATA_DEFAULT FROM USER_TAB_COLUMNS'
        . ' WHERE TABLE_NAME=? ORDER BY COLUMN_ID');
    $q->execute([$name]);
    return $q->fetchAll(PDO::FETCH_ASSOC);
};
$fingerprint = static function (string $name, array $names) use ($pdo, $schema): array {
    foreach (array_merge([$name], $names) as $identifier) {
        if (!preg_match('/^[A-Z][A-Z0-9_]*$/D', $identifier)) {
            throw new RuntimeException('Invalid snapshot identifier');
        }
    }
    $types = []; foreach ($GLOBALS['metadata_for_fingerprint'] ?? [] as $col) $types[strtoupper($col['column_name'])] = strtoupper($col['data_type']);
    $sql = 'SELECT ' . implode(',', array_map(static function ($s) use ($types) {
        return in_array($types[$s] ?? '', ['DECIMAL', 'NUMERIC', 'NUMBER'], true)
            ? 'CAST("' . $s . '" AS VARCHAR(128))' : '"' . $s . '"';
    }, $names))
        . ' FROM ' . $schema . '.' . $name . ' ORDER BY ID';
    $q = $pdo->query($sql);
    $hash = hash_init('sha256');
    $count = 0;
    while ($row = $q->fetch(PDO::FETCH_NUM)) {
        foreach ($row as &$value) if (is_resource($value)) $value = stream_get_contents($value);
        unset($value);
        hash_update($hash, serialize($row) . "\n");
        $count++;
    }
    return ['rows' => $count, 'sha256' => hash_final($hash)];
};
$add = static function (string $name) use ($pdo, $schema, $definitions): void {
    $parts = [];
    foreach ($definitions as $column => $definition) $parts[] = $column . ' ' . $definition;
    $pdo->exec('ALTER TABLE ' . $schema . '.' . $name . ' ADD (' . implode(',', $parts) . ')');
};
$validateDefaults = static function (string $name) use ($pdo, $schema, $definitions, $nullable): void {
    $invalid = [];
    foreach ($definitions as $column => $definition) {
        if (in_array($column, $nullable, true)) $invalid[] = $column . ' IS NOT NULL';
        elseif (strpos($definition, 'VARCHAR') === 0) $invalid[] = '(' . $column . " IS NULL OR " . $column . "<>'')";
        else $invalid[] = '(' . $column . ' IS NULL OR ' . $column . '<>0)';
    }
    $count = (int)$pdo->query('SELECT COUNT(*) FROM ' . $schema . '.' . $name
        . ' WHERE ' . implode(' OR ', $invalid))->fetchColumn();
    if ($count !== 0) throw new RuntimeException('New columns have unexpected values');
};
$setNotNull = static function (string $name) use ($pdo, $schema, $definitions, $nullable): void {
    foreach ($definitions as $column => $definition) {
        if (!in_array($column, $nullable, true)) {
            $pdo->exec('ALTER TABLE ' . $schema . '.' . $name . ' ALTER COLUMN '
                . $column . ' SET NOT NULL');
        }
    }
};
$metadata = $columns($table);
$GLOBALS['metadata_for_fingerprint'] = $metadata;
if (!$metadata) throw new RuntimeException('Approved test table not found');
$names = array_map('strtoupper', array_column($metadata, 'column_name'));
$missing = array_values(array_diff(array_keys($definitions), $names));
if ($action === 'preflight') {
    $counts = [];
    foreach (['USER_INDEXES' => 'indexes', 'USER_CONSTRAINTS' => 'constraints',
        'USER_TRIGGERS' => 'triggers'] as $view => $label) {
        $q = $pdo->prepare('SELECT COUNT(*) FROM ' . $view . ' WHERE TABLE_NAME=?');
        $q->execute([$table]); $counts[$label] = (int)$q->fetchColumn();
    }
    echo json_encode(['action' => $action, 'columns' => count($names),
        'missing' => $missing, 'objects' => $counts,
        'data' => $fingerprint($table, $names)]) . PHP_EOL;
    return;
}
// User explicitly waived backup/restore. Only the approved missing column may change.
$objectNames = static function () use ($pdo, $table): array {
    $result = [];
    foreach (['USER_INDEXES' => 'INDEX_NAME', 'USER_CONSTRAINTS' => 'CONSTRAINT_NAME',
        'USER_TRIGGERS' => 'TRIGGER_NAME'] as $view => $column) {
        $q = $pdo->prepare('SELECT ' . $column . ' FROM ' . $view . ' WHERE TABLE_NAME=? ORDER BY ' . $column);
        $q->execute([$table]); $result[$view] = $q->fetchAll(PDO::FETCH_COLUMN);
    }
    return $result;
};
$verify = static function (array $all) use ($validateDefaults, $table): void {
    $target = array_values(array_filter($all, static function ($c) { return strtoupper($c['column_name']) === 'BANK_USER_NAME'; }));
    if (count($target) !== 1) throw new RuntimeException('Expected one bank-name column');
    $c = $target[0];
    if (strtoupper($c['data_type']) !== 'VARCHAR' || (int)$c['char_length'] !== 255
        || $c['char_used'] !== 'C' || $c['nullable'] !== 'N'
        || trim((string)$c['data_default']) !== "''") throw new RuntimeException('Source contract mismatch');
    $validateDefaults($table);
};
if ($action !== 'apply') throw new RuntimeException('Unknown stage');
if (!$missing) {
    $verify($metadata);
    echo json_encode(['action' => 'apply', 'status' => 'already-applied-verified', 'ddl_executed' => 0]) . PHP_EOL;
    return;
}
if ($missing !== ['BANK_USER_NAME'] || count($names) !== 111) throw new RuntimeException('Unexpected pre-migration shape');
$before = $fingerprint($table, $names);
if ($before !== ['rows' => 23617, 'sha256' => '6f9e49c661a897a0af84dcac195ab0a654be656df3fc254f03d5b9330620da9f']) {
    throw new RuntimeException('Original data changed since preflight; re-audit before DDL');
}
$objects = $objectNames();
if ($objects['USER_TRIGGERS']) throw new RuntimeException('Unexpected trigger');
$started = microtime(true);
$add($table);
$validateDefaults($table);
$setNotNull($table);
$pdo->exec("COMMENT ON COLUMN ZHCT_APP.YDY_MEAL_ORDER.BANK_USER_NAME IS '张家港银行核销返回的消费人姓名'");
$after = $columns($table);
if (count($after) !== 112 || array_slice($after, 0, 111) !== $metadata) throw new RuntimeException('Original metadata changed');
$verify($after);
$final = $fingerprint($table, $names);
if ($final !== $before || $objectNames() !== $objects) throw new RuntimeException('Original data/objects changed');
echo json_encode(['action' => 'apply', 'status' => 'passed', 'backup_restore_waived_by_user' => true,
    'columns_before' => 111, 'columns_after' => 112, 'rows' => $final['rows'],
    'original_projection_before' => $before['sha256'], 'original_projection_after' => $final['sha256'],
    'original_metadata_unchanged' => true, 'original_objects_unchanged' => true,
    'new_column' => ['type' => 'VARCHAR', 'characters' => 255, 'nullable' => false, 'default' => ''],
    'new_column_all_rows_default_empty' => true, 'ddl_executed' => 3,
    'elapsed_seconds' => round(microtime(true) - $started, 3)]) . PHP_EOL;
