Source: config/migrations/20260927-member-lookup-key.js

export const id = '20260927-member-lookup-key';
export const name = 'Member: generic lookup slot (lookup_key) replacing credential_id';

/**
 * `Member.credential_id` (20260926) war typ-gebunden benannt: eine Spalte, die
 * genau ein Objekttyp mit wenigen Zeilen benutzt. Diese Migration ersetzt sie
 * durch einen **generischen Lookup-Slot**.
 *
 * ## Konvention (neu, hier festgehalten)
 *
 * `Member.lookup_key` ist der **eine opake externe Schlüssel** eines Objekts:
 * ein Wert, der von außen kommt (Client, Registrierung, Import) und über den
 * das Objekt gesucht wird — NICHT die Objekt-UID.
 *
 *  - **Ein Slot pro Objekt, global eindeutig.** Die Bedeutung ergibt sich aus
 *    `ObjectBase.Type`, nicht aus dem Spaltennamen. Kein Typ-spezifisches Feld,
 *    keine zweite Spalte für den nächsten Typ.
 *  - **Die Werte sind präfixiert** (`rc_…` für Runner-Credentials). Deshalb
 *    genügt ein globaler UNIQUE statt `(Type, key)` — Letzteres bräuchte `Type`
 *    in `Member` (Denormalisierung) und bringt für einen Zufallswert nichts.
 *  - **Kein anderer Zweck.** `Member.Data` trägt den heißen Zustand (Heartbeat,
 *    Capacity); `lookup_key` trägt nur den Suchschlüssel. Beides zu vermischen
 *    macht den Unique-Konflikt zur Zufallsfehlerquelle an unerwarteten Stellen.
 *
 * Die Rolle ist damit `SortName`/`FullTextIndex`/`PhonetikIndex`/`Geo`
 * gleichgestellt: eine ausgezogene Spalte, die Lookup oder Sort dient — die
 * Member-Konvention (080-Workspaces/040-Ist-Zustand: „heißer Zustand im
 * Data-JSON, ausgezogene Spalten nur für Lookup/Sort").
 *
 * ## Ablauf
 *
 *  1. `lookup_key` an `Member` anlegen (varchar(128): ein generischer Slot
 *     sollte nicht auf die Länge seines ersten Nutzers dimensioniert sein).
 *  2. UNIQUE `member_lookup_key_uq` anlegen.
 *  3. Bestehende `credential_id`-Werte übernehmen, ihren Index und die Spalte
 *     entfernen.
 *
 * Idempotent: eine frische DB (initTables.sql kennt bereits `lookup_key`, kein
 * `credential_id`) überspringt alles; eine laufende DB landet im selben
 * Zustand. Hinweis: `20260926` legt `credential_id` weiterhin an — auf einem
 * frischen Boot entsteht sie leer und wird hier wieder entfernt, weil die
 * Migration bereits appliziert ist und nicht mehr geändert werden darf.
 */

const hasColumn = async (query, table, column) => {
    const rows = await query(
        `SELECT COUNT(*) AS c FROM information_schema.columns
         WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?`,
        [table, column],
    );
    return Number(rows[0]?.c) > 0;
};

const hasIndex = async (query, table, indexName) => {
    const rows = await query(
        `SELECT COUNT(*) AS c FROM information_schema.statistics
         WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?`,
        [table, indexName],
    );
    return Number(rows[0]?.c) > 0;
};

export const migrate = async ({ query }) => {
    // ------------------------------------------- 1. Generischer Lookup-Slot
    if (!(await hasColumn(query, 'Member', 'lookup_key'))) {
        await query(
            `ALTER TABLE \`Member\` ADD COLUMN \`lookup_key\` varchar(128) DEFAULT NULL`,
            [],
        );
    }
    if (!(await hasIndex(query, 'Member', 'member_lookup_key_uq'))) {
        await query(
            `ALTER TABLE \`Member\` ADD UNIQUE KEY \`member_lookup_key_uq\` (\`lookup_key\`)`,
            [],
        );
    }

    // ------------------------------- 2. Bestand aus credential_id übernehmen
    // Die alte Spalte war UNIQUE, also kollidiert die Übernahme nicht. Erst
    // kopieren, dann Index, dann Spalte — in dieser Reihenfolge, weil der
    // UNIQUE auf lookup_key die Kopie sonst nicht absichern würde.
    if (await hasColumn(query, 'Member', 'credential_id')) {
        await query(
            `UPDATE \`Member\` SET \`lookup_key\` = \`credential_id\`
             WHERE \`credential_id\` IS NOT NULL AND \`credential_id\` <> ''`,
            [],
        );
        if (await hasIndex(query, 'Member', 'member_credential_id_uq')) {
            await query(`ALTER TABLE \`Member\` DROP INDEX \`member_credential_id_uq\``, []);
        }
        await query(`ALTER TABLE \`Member\` DROP COLUMN \`credential_id\``, []);
    }
};