"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RemoveNetworkIdFromAddresses1789600000000 = void 0;
const typeorm_1 = require("typeorm");
const UTXO_CODES = ['btc', 'ltc', 'doge', 'bch', 'dash', 'dgb', 'zec', 'rvn', 'dcr'];
const BATCH_SIZE = 500;
/**
 * Refactors the addresses table so that a wallet has exactly one address per
 * address type instead of one address per network.
 *
 * - Updates networks.address_type for UTXO networks to the bitcoin-{code} form.
 * - Rewrites addresses.type based on the network each address belonged to.
 * - Collapses duplicate addresses per (wallet_id, type), keeping the oldest
 *   created address as the survivor.
 * - Redirects transaction from_address_id / to_address_id / data.to references
 *   from deleted addresses to the survivor.
 * - Drops the now-redundant network_id column from addresses.
 * - Adds a unique constraint on (wallet_id, type) to prevent future duplicates.
 */
class RemoveNetworkIdFromAddresses1789600000000 {
    name = 'RemoveNetworkIdFromAddresses1789600000000';
    async up(queryRunner) {
        await queryRunner.startTransaction();
        try {
            const table = await queryRunner.getTable('addresses');
            // If network_id is already gone, the schema was synchronized after the
            // refactor. We only need to make sure the unique constraint exists.
            if (table?.findColumnByName('network_id')) {
                // 1. Make sure UTXO networks use the per-symbol address type. This keeps
                // the networks table aligned with the new address model.
                const utxoInClause = UTXO_CODES.map(() => '?').join(',');
                await queryRunner.query(`
            UPDATE networks
            SET address_type = CONCAT('bitcoin-', LOWER(code))
            WHERE LOWER(code) IN (${utxoInClause})
              AND chain_id IS NULL
          `, UTXO_CODES);
                // 2. Rewrite address types based on the linked network.
                await queryRunner.query(`
            UPDATE addresses a
            JOIN networks n ON a.network_id = n.id
            SET a.type = CASE
              WHEN n.chain_id IS NOT NULL THEN 'evm'
              WHEN LOWER(n.code) IN (${utxoInClause}) THEN CONCAT('bitcoin-', LOWER(n.code))
              ELSE n.address_type
            END
            WHERE a.network_id IS NOT NULL
          `, UTXO_CODES);
                // 3. Build a redirect map: for each (wallet_id, type) keep the oldest
                // address and point every duplicate at the survivor.
                const addressRows = await queryRunner.query(`SELECT id, wallet_id, type, is_primary, created_at FROM addresses ORDER BY created_at ASC`);
                const survivorByKey = new Map();
                const redirectMap = new Map();
                const survivorsToMakePrimary = new Set();
                for (const row of addressRows) {
                    const key = `${row.wallet_id}:${row.type}`;
                    const existingSurvivor = survivorByKey.get(key);
                    if (!existingSurvivor) {
                        survivorByKey.set(key, row.id);
                        continue;
                    }
                    redirectMap.set(row.id, existingSurvivor);
                    if (row.is_primary === 1 || row.is_primary === true) {
                        survivorsToMakePrimary.add(existingSurvivor);
                    }
                }
                // 4. Promote survivors to primary if any of their duplicates were primary.
                if (survivorsToMakePrimary.size > 0) {
                    const survivorIds = Array.from(survivorsToMakePrimary);
                    for (let i = 0; i < survivorIds.length; i += BATCH_SIZE) {
                        const batch = survivorIds.slice(i, i + BATCH_SIZE);
                        const placeholders = batch.map(() => '?').join(',');
                        await queryRunner.query(`UPDATE addresses SET is_primary = TRUE WHERE id IN (${placeholders})`, batch);
                    }
                }
                const redirects = Array.from(redirectMap.entries());
                // 5. Redirect transaction address references in batches.
                if (redirects.length > 0) {
                    const oldIds = redirects.map(([oldId]) => oldId);
                    for (let i = 0; i < redirects.length; i += BATCH_SIZE) {
                        const batch = redirects.slice(i, i + BATCH_SIZE);
                        const batchOldIds = batch.map(([oldId]) => oldId);
                        const casePlaceholders = batch.map(() => 'WHEN ? THEN ?').join(' ');
                        const inPlaceholders = batchOldIds.map(() => '?').join(',');
                        const params = [...batch.flat(), ...batchOldIds];
                        await queryRunner.query(`UPDATE transactions
               SET from_address_id = CASE from_address_id ${casePlaceholders} ELSE from_address_id END
               WHERE from_address_id IN (${inPlaceholders})`, params);
                        await queryRunner.query(`UPDATE transactions
               SET to_address_id = CASE to_address_id ${casePlaceholders} ELSE to_address_id END
               WHERE to_address_id IN (${inPlaceholders})`, params);
                    }
                    // 6. Redirect address IDs stored inside transactions.data JSON paths.
                    const oldIdSet = new Set(oldIds);
                    const txRows = await queryRunner.query(`SELECT id, data FROM transactions WHERE JSON_CONTAINS_PATH(data, 'one', '$[*].to')`);
                    for (const tx of txRows) {
                        const data = typeof tx.data === 'string' ? JSON.parse(tx.data) : tx.data;
                        let changed = false;
                        for (const path of data) {
                            if (path && typeof path === 'object' && 'to' in path && oldIdSet.has(path.to)) {
                                path.to = redirectMap.get(path.to);
                                changed = true;
                            }
                        }
                        if (changed) {
                            await queryRunner.query(`UPDATE transactions SET data = ? WHERE id = ?`, [
                                JSON.stringify(data),
                                tx.id,
                            ]);
                        }
                    }
                }
                // 7. Delete the duplicate address rows.
                if (redirects.length > 0) {
                    const idsToDelete = redirects.map(([oldId]) => oldId);
                    for (let i = 0; i < idsToDelete.length; i += BATCH_SIZE) {
                        const batch = idsToDelete.slice(i, i + BATCH_SIZE);
                        const placeholders = batch.map(() => '?').join(',');
                        await queryRunner.query(`DELETE FROM addresses WHERE id IN (${placeholders})`, batch);
                    }
                }
                // 8. Drop the network_id column (and its foreign key if present).
                const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.includes('network_id'));
                if (foreignKey) {
                    await queryRunner.dropForeignKey('addresses', foreignKey);
                }
                await queryRunner.dropColumn('addresses', 'network_id');
            }
            // 9. Enforce the new invariant: one address per (wallet_id, type).
            const hasUniqueConstraint = Boolean(table?.uniques.some((unique) => unique.name === 'uq_addresses_wallet_id_type') ||
                table?.indices.some((index) => index.name === 'uq_addresses_wallet_id_type'));
            if (!hasUniqueConstraint) {
                await queryRunner.query(`ALTER TABLE addresses ADD CONSTRAINT uq_addresses_wallet_id_type UNIQUE (wallet_id, type)`);
            }
            await queryRunner.commitTransaction();
        }
        catch (error) {
            await queryRunner.rollbackTransaction();
            throw error;
        }
    }
    async down(queryRunner) {
        const table = await queryRunner.getTable('addresses');
        const hasUniqueConstraint = Boolean(table?.uniques.some((unique) => unique.name === 'uq_addresses_wallet_id_type') ||
            table?.indices.some((index) => index.name === 'uq_addresses_wallet_id_type'));
        if (hasUniqueConstraint) {
            await queryRunner.query(`ALTER TABLE addresses DROP CONSTRAINT uq_addresses_wallet_id_type`);
        }
        if (!table?.findColumnByName('network_id')) {
            await queryRunner.addColumn('addresses', new typeorm_1.TableColumn({
                name: 'network_id',
                type: 'varchar',
                length: '255',
                isNullable: true,
            }));
        }
        // Note: collapsed duplicate addresses cannot be restored by this down migration.
    }
}
exports.RemoveNetworkIdFromAddresses1789600000000 = RemoveNetworkIdFromAddresses1789600000000;
//# sourceMappingURL=1789600000000-RemoveNetworkIdFromAddresses.js.map