"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HubExport = void 0;
const path = require("path");
const fs = require("fs");
const fse = require("fs-extra");
const http = require("http");
const logger_1 = require("../logger");
const types_1 = require("./types");
const registry_1 = require("./registry");
const error_1 = require("../error");
const hub_1 = require("./hub");
const downloadableEmulators_1 = require("./downloadableEmulators");
const rimraf = require("rimraf");
const track_1 = require("../track");
class HubExport {
    constructor(projectId, options) {
        this.projectId = projectId;
        this.options = options;
        this.exportPath = options.path;
        this.tmpDir = fs.mkdtempSync(`firebase-export-${new Date().getTime()}`);
    }
    static readMetadata(exportPath) {
        const metadataPath = path.join(exportPath, this.METADATA_FILE_NAME);
        if (!fs.existsSync(metadataPath)) {
            return undefined;
        }
        return JSON.parse(fs.readFileSync(metadataPath, "utf8").toString());
    }
    async exportAll() {
        const toExport = types_1.ALL_EMULATORS.filter(shouldExport);
        if (toExport.length === 0) {
            throw new error_1.FirebaseError("No running emulators support import/export.");
        }
        const metadata = {
            version: hub_1.EmulatorHub.CLI_VERSION,
        };
        if (shouldExport(types_1.Emulators.FIRESTORE)) {
            metadata.firestore = {
                version: (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.FIRESTORE).version,
                path: "firestore_export",
                metadata_file: "firestore_export/firestore_export.overall_export_metadata",
            };
            await this.exportFirestore(metadata);
        }
        if (shouldExport(types_1.Emulators.DATABASE)) {
            metadata.database = {
                version: (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.DATABASE).version,
                path: "database_export",
            };
            await this.exportDatabase(metadata);
        }
        if (shouldExport(types_1.Emulators.AUTH)) {
            metadata.auth = {
                version: hub_1.EmulatorHub.CLI_VERSION,
                path: "auth_export",
            };
            await this.exportAuth(metadata);
        }
        if (shouldExport(types_1.Emulators.STORAGE)) {
            metadata.storage = {
                version: hub_1.EmulatorHub.CLI_VERSION,
                path: "storage_export",
            };
            await this.exportStorage(metadata);
        }
        if (!fs.existsSync(this.exportPath)) {
            fs.mkdirSync(this.exportPath);
        }
        void (0, track_1.trackEmulator)("emulator_export", {
            initiated_by: this.options.initiatedBy,
            emulator_name: types_1.Emulators.HUB,
        });
        const metadataPath = path.join(this.tmpDir, HubExport.METADATA_FILE_NAME);
        fs.writeFileSync(metadataPath, JSON.stringify(metadata, undefined, 2));
        logger_1.logger.debug(`hubExport: swapping ${this.tmpDir} with ${this.exportPath}`);
        rimraf.sync(this.exportPath);
        fse.moveSync(this.tmpDir, this.exportPath);
    }
    async exportFirestore(metadata) {
        void (0, track_1.trackEmulator)("emulator_export", {
            initiated_by: this.options.initiatedBy,
            emulator_name: types_1.Emulators.FIRESTORE,
        });
        const firestoreExportBody = {
            database: `projects/${this.projectId}/databases/(default)`,
            export_directory: this.tmpDir,
            export_name: metadata.firestore.path,
        };
        await registry_1.EmulatorRegistry.client(types_1.Emulators.FIRESTORE).post(`/emulator/v1/projects/${this.projectId}:export`, firestoreExportBody);
    }
    async exportDatabase(metadata) {
        const databaseEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
        const client = registry_1.EmulatorRegistry.client(types_1.Emulators.DATABASE, { auth: true });
        const inspectURL = `/.inspect/databases.json`;
        const inspectRes = await client.get(inspectURL, {
            queryParams: { ns: this.projectId },
        });
        const namespaces = inspectRes.body.map((instance) => instance.name);
        const namespacesToExport = [];
        for (const ns of namespaces) {
            const checkDataPath = `/.json`;
            const checkDataRes = await client.get(checkDataPath, {
                queryParams: {
                    ns,
                    shallow: "true",
                    limitToFirst: 1,
                },
            });
            if (checkDataRes.body !== null) {
                namespacesToExport.push(ns);
            }
            else {
                logger_1.logger.debug(`Namespace ${ns} contained null data, not exporting`);
            }
        }
        for (const ns of databaseEmulator.getImportedNamespaces()) {
            if (!namespacesToExport.includes(ns)) {
                logger_1.logger.debug(`Namespace ${ns} was imported, exporting.`);
                namespacesToExport.push(ns);
            }
        }
        void (0, track_1.trackEmulator)("emulator_export", {
            initiated_by: this.options.initiatedBy,
            emulator_name: types_1.Emulators.DATABASE,
            count: namespacesToExport.length,
        });
        const dbExportPath = path.join(this.tmpDir, metadata.database.path);
        if (!fs.existsSync(dbExportPath)) {
            fs.mkdirSync(dbExportPath);
        }
        const { host, port } = databaseEmulator.getInfo();
        for (const ns of namespacesToExport) {
            const exportFile = path.join(dbExportPath, `${ns}.json`);
            logger_1.logger.debug(`Exporting database instance: ${ns} to ${exportFile}`);
            await fetchToFile({
                host,
                port,
                path: `/.json?ns=${ns}&format=export`,
                headers: { Authorization: "Bearer owner" },
            }, exportFile);
        }
    }
    async exportAuth(metadata) {
        void (0, track_1.trackEmulator)("emulator_export", {
            initiated_by: this.options.initiatedBy,
            emulator_name: types_1.Emulators.AUTH,
        });
        const { host, port } = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH).getInfo();
        const authExportPath = path.join(this.tmpDir, metadata.auth.path);
        if (!fs.existsSync(authExportPath)) {
            fs.mkdirSync(authExportPath);
        }
        const accountsFile = path.join(authExportPath, "accounts.json");
        logger_1.logger.debug(`Exporting auth users in Project ${this.projectId} to ${accountsFile}`);
        await fetchToFile({
            host,
            port,
            path: `/identitytoolkit.googleapis.com/v1/projects/${this.projectId}/accounts:batchGet?maxResults=-1`,
            headers: { Authorization: "Bearer owner" },
        }, accountsFile);
        const configFile = path.join(authExportPath, "config.json");
        logger_1.logger.debug(`Exporting project config in Project ${this.projectId} to ${accountsFile}`);
        await fetchToFile({
            host,
            port,
            path: `/emulator/v1/projects/${this.projectId}/config`,
            headers: { Authorization: "Bearer owner" },
        }, configFile);
    }
    async exportStorage(metadata) {
        const storageExportPath = path.join(this.tmpDir, metadata.storage.path);
        if (fs.existsSync(storageExportPath)) {
            fse.removeSync(storageExportPath);
        }
        fs.mkdirSync(storageExportPath, { recursive: true });
        const storageExportBody = {
            path: storageExportPath,
            initiatedBy: this.options.initiatedBy,
        };
        const res = await registry_1.EmulatorRegistry.client(types_1.Emulators.STORAGE).request({
            method: "POST",
            path: "/internal/export",
            headers: { "Content-Type": "application/json" },
            body: storageExportBody,
            responseType: "stream",
            resolveOnHTTPError: true,
        });
        if (res.status >= 400) {
            throw new error_1.FirebaseError(`Failed to export storage: ${await res.response.text()}`);
        }
    }
}
exports.HubExport = HubExport;
HubExport.METADATA_FILE_NAME = "firebase-export-metadata.json";
function fetchToFile(options, path) {
    const writeStream = fs.createWriteStream(path);
    return new Promise((resolve, reject) => {
        http
            .get(options, (response) => {
            response.pipe(writeStream, { end: true }).once("close", resolve);
        })
            .on("error", reject);
    });
}
function shouldExport(e) {
    return types_1.IMPORT_EXPORT_EMULATORS.includes(e) && registry_1.EmulatorRegistry.isRunning(e);
}