"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const nedb_1 = __importDefault(require("nedb"));
const nopt_1 = __importDefault(require("nopt"));
const path_1 = __importDefault(require("path"));
const fs_1 = require("fs");
const logging_1 = require("../logging");
const util_1 = require("util");
const PgDataStore_1 = require("../datastore/postgres/PgDataStore");
const IrcRoom_1 = require("../models/IrcRoom");
const matrix_appservice_bridge_1 = require("matrix-appservice-bridge");
const IrcClientConfig_1 = require("../models/IrcClientConfig");
const IrcServer_1 = require("../irc/IrcServer");
const log = (0, logging_1.simpleLogger)();
async function migrate(roomsFind, usersFind, pgStore, typesToRun) {
    const migrateChannels = async () => {
        const channelEntries = await roomsFind({ "remote.type": "channel" });
        log.info(`Migrating ${channelEntries.length} channels`);
        for (const entry of channelEntries) {
            if (entry.id.startsWith("PM")) {
                continue; // Some entries are mis-labeled as channels when they are PMs
            }
            try {
                await pgStore.upsertRoom(entry.data.origin, "channel", entry.remote.domain, entry.remote.channel, entry.matrix_id, JSON.stringify(entry.remote), JSON.stringify(entry.matrix));
                log.info(`Migrated channel ${entry.remote.channel}`);
            }
            catch (ex) {
                log.error(`Failed to migrate channel ${entry.remote.channel} ${ex.message}`);
                log.error(JSON.stringify(entry));
                throw ex;
            }
        }
        log.info("Migrated channels");
    };
    const migrateCounter = async () => {
        log.info(`Migrating ipv6 counter`);
        const counterEntry = await usersFind({ "type": "matrix", "id": "config" });
        for (const [key, serverCounter] of Object.entries(counterEntry?.[0]?.data || {})) {
            if (!key.startsWith('ipv6_counter_')) {
                continue;
            }
            const networkName = key.substring('ipv6_counter_'.length).replace(/_/g, '.');
            for (const [homeserver, counter] of Object.entries(serverCounter)) {
                await pgStore.setIpv6Counter(counter, new IrcServer_1.IrcServer(networkName, IrcServer_1.IrcServer.DEFAULT_CONFIG, ''), homeserver === '*' ? null : homeserver);
            }
        }
        log.info("Migrated ipv6 counter");
    };
    const migrateAdminRooms = async () => {
        const entries = await roomsFind({ "matrix.extras.admin_id": { $exists: true } });
        log.info(`Migrating ${entries.length} admin rooms`);
        for (const entry of entries) {
            await pgStore.storeAdminRoom(new matrix_appservice_bridge_1.MatrixRoom(entry.matrix_id), entry.matrix.extras.admin_id);
        }
        log.info("Migrated admin rooms");
    };
    const migrateUserFeatures = async () => {
        const entries = await usersFind({ "type": "matrix", "data.features": { $exists: true } });
        log.info(`Migrating ${entries.length} user features`);
        for (const entry of entries) {
            await pgStore.storeUserFeatures(entry.id, entry.data.features);
        }
        log.info("Migrated user features");
    };
    const migrateUserConfiguration = async () => {
        const entries = await usersFind({ "type": "matrix", "data.client_config": { $exists: true } });
        log.info(`Migrating ${entries.length} user configs`);
        for (const entry of entries) {
            const configs = entry.data.client_config;
            for (const network of Object.keys(configs)) {
                const config = configs[network];
                const password = config.password;
                delete config.password; // We store this seperate now.
                await pgStore.storeIrcClientConfig(new IrcClientConfig_1.IrcClientConfig(entry.id, network, config));
                await pgStore.storePass(entry.id, network, password, false);
            }
            await pgStore.storeUserFeatures(entry.id, entry.data.features);
        }
        log.info("Migrated user configs");
    };
    const migrateUsers = async () => {
        const entries = await usersFind({ "type": "matrix" });
        log.info(`Migrating ${entries.length} users`);
        for (const entry of entries) {
            // We store these seperately.
            delete entry.data.client_config;
            delete entry.data.features;
            await pgStore.storeMatrixUser(new matrix_appservice_bridge_1.MatrixUser(entry.id, entry.data));
        }
        log.info("Migrated users");
    };
    const migratePMs = async () => {
        const entries = await roomsFind({ "remote.type": "pm" });
        log.info(`Migrating ${entries.length} PM rooms`);
        for (const entry of entries.reverse()) {
            // We previously allowed unlimited numbers of PM rooms, but the bridge now mandates
            // that only one DM room may exist for a given mxid<->nick. Reverse the entries, and
            // ignore any future collisions to ensure that we only use the latest.
            try {
                await pgStore.setPmRoom(
                // IrcRoom will only ever use the domain property
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                new IrcRoom_1.IrcRoom({ domain: entry.remote.domain }, entry.remote.channel), new matrix_appservice_bridge_1.MatrixRoom(entry.matrix_id), entry.data.real_user_id, entry.data.virtual_user_id);
            }
            catch (ex) {
                log.warn("Not migrating %s", entry.matrix_id);
            }
        }
        log.info("Migrated PMs");
    };
    if (typesToRun.includes("channels")) {
        await migrateChannels();
    }
    if (typesToRun.includes("counter")) {
        await migrateCounter();
    }
    if (typesToRun.includes("adminrooms")) {
        await migrateAdminRooms();
    }
    if (typesToRun.includes("features")) {
        await migrateUserFeatures();
    }
    if (typesToRun.includes("config")) {
        await migrateUserConfiguration();
    }
    if (typesToRun.includes("users")) {
        await migrateUsers();
    }
    if (typesToRun.includes("pms")) {
        await migratePMs();
    }
}
async function main() {
    const opts = (0, nopt_1.default)({
        dbdir: path_1.default,
        connectionString: String,
        verbose: Boolean,
        privateKey: path_1.default,
        types: Array,
    }, {
        f: "--dbdir",
        c: "--connectionString",
        p: "--privateKey",
        v: "--verbose",
        t: "--types",
    }, process.argv, 2);
    const typesToRun = opts.types || ["channels", "counter", "adminrooms", "features", "config", "users", "pms"];
    if (opts.dbdir === undefined || opts.connectionString === undefined) {
        log.error("Missing --dbdir or --connectionString or --domain");
        process.exit(1);
    }
    if (opts.privateKey === undefined) {
        log.warn("Missing privateKey, passwords will not be migrated");
    }
    if (opts.verbose) {
        log.level = "verbose";
    }
    try {
        await Promise.all(["rooms.db", "users.db"].map(async (f) => {
            const p = path_1.default.join(opts.dbdir, f);
            const stats = await fs_1.promises.stat(p);
            if (stats.isDirectory() && stats.size > 0) {
                throw Error(`${p} must be a file`);
            }
        }));
    }
    catch (ex) {
        log.error("Missing a file: %s", ex);
        process.exit(1);
    }
    // Domain isn't used for any of our operations
    const pgStore = new PgDataStore_1.PgDataStore("", opts.connectionString, opts.privateKey);
    try {
        await pgStore.ensureSchema();
    }
    catch (ex) {
        log.warn("Could not ensure schema version: %s", ex);
        process.exit(1);
    }
    const rooms = new nedb_1.default({ filename: path_1.default.join(opts.dbdir, "rooms.db"), autoload: true });
    const users = new nedb_1.default({ filename: path_1.default.join(opts.dbdir, "users.db"), autoload: true });
    const roomsFind = (0, util_1.promisify)(rooms.find).bind(rooms);
    const usersFind = (0, util_1.promisify)(users.find).bind(users);
    const time = Date.now();
    log.info("Starting migration");
    await migrate(roomsFind, usersFind, pgStore, typesToRun);
    log.info("Finished migration at %sms", Date.now() - time);
}
main().catch((ex) => {
    log.error("Failed to run migration script: %s", ex);
});
//# sourceMappingURL=migrate-db-to-pgres.js.map