/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module RpcInterface
 */
import { assert, BeDuration, IModelStatus, Logger } from "@itwin/core-bentley";
import { IModelError, IModelVersion, RpcPendingResponse, SyncMode, } from "@itwin/core-common";
import { BackendLoggerCategory } from "../BackendLoggerCategory";
import { BriefcaseManager } from "../BriefcaseManager";
import { CheckpointManager } from "../CheckpointManager";
import { BriefcaseDb, IModelDb, SnapshotDb } from "../IModelDb";
import { IModelHost } from "../IModelHost";
import { IModelJsFs } from "../IModelJsFs";
import { _hubAccess } from "../internal/Symbols";
const loggerCategory = BackendLoggerCategory.IModelDb;
/**
 * Utility to open the iModel for RPC interfaces
 * @internal
 */
export class RpcBriefcaseUtility {
    static async downloadAndOpen(args) {
        const { activity, tokenProps } = args;
        const accessToken = activity.accessToken;
        assert(undefined !== tokenProps.iModelId);
        const iModelId = tokenProps.iModelId;
        let myBriefcaseIds;
        if (args.syncMode === SyncMode.PullOnly) {
            myBriefcaseIds = [0]; // PullOnly means briefcaseId 0
        }
        else {
            // check with iModelHub and see if we already have acquired any briefcaseIds
            myBriefcaseIds = await IModelHost[_hubAccess].getMyBriefcaseIds({ accessToken, iModelId });
        }
        const resolvers = args.fileNameResolvers ?? [(arg) => BriefcaseManager.getFileName(arg)];
        // see if we can open any of the briefcaseIds we already acquired from iModelHub
        if (resolvers) {
            for (const resolver of resolvers) {
                for (const briefcaseId of myBriefcaseIds) {
                    const fileName = resolver({ briefcaseId, iModelId });
                    if (IModelJsFs.existsSync(fileName)) {
                        const briefcaseDb = BriefcaseDb.findByFilename(fileName);
                        if (briefcaseDb !== undefined) {
                            if (briefcaseDb.isBriefcaseDb()) {
                                return briefcaseDb;
                            }
                            else {
                                throw new IModelError(IModelStatus.AlreadyOpen, "iModel is already open as a SnapshotDb");
                            }
                        }
                        try {
                            if (args.forceDownload)
                                throw new Error(); // causes delete below
                            const db = await BriefcaseDb.open({ fileName });
                            if (db.changeset.id !== tokenProps.changeset?.id) {
                                assert(undefined !== tokenProps.changeset);
                                const toIndex = tokenProps.changeset?.index ??
                                    (await IModelHost[_hubAccess].getChangesetFromVersion({ accessToken, iModelId, version: IModelVersion.asOfChangeSet(tokenProps.changeset.id) })).index;
                                await BriefcaseManager.pullAndApplyChangesets(db, { accessToken, toIndex });
                            }
                            return db;
                        }
                        catch (error) {
                            if (!(error.errorNumber === IModelStatus.AlreadyOpen))
                                // somehow we have this briefcaseId and the file exists, but we can't open it. Delete it.
                                await BriefcaseManager.deleteBriefcaseFiles(fileName, accessToken);
                        }
                    }
                }
            }
        }
        // no local briefcase available. Download one and open it.
        assert(undefined !== tokenProps.iTwinId);
        const request = {
            accessToken,
            iTwinId: tokenProps.iTwinId,
            iModelId,
            briefcaseId: args.syncMode === SyncMode.PullOnly ? 0 : undefined, // if briefcaseId is undefined, we'll acquire a new one.
        };
        const props = await BriefcaseManager.downloadBriefcase(request);
        return BriefcaseDb.open(props);
    }
    static _briefcasePromises = new Map();
    static async openBriefcase(args) {
        const key = `${args.tokenProps.iModelId}:${args.tokenProps.changeset?.id}:${args.tokenProps.changeset?.index}:${args.syncMode}`;
        const cachedPromise = this._briefcasePromises.get(key);
        if (cachedPromise)
            return cachedPromise;
        try {
            const briefcasePromise = this.downloadAndOpen(args); // save the fact that we're working on downloading so if we timeout, we'll reuse this request.
            this._briefcasePromises.set(key, briefcasePromise);
            return await briefcasePromise;
        }
        finally {
            this._briefcasePromises.delete(key); // the download and open is now done
        }
    }
    /** find a previously opened iModel for RPC.
     * @param accessToken necessary (only) for V2 checkpoints to refresh access token in daemon if it has expired. We use the accessToken of the current RPC request
     * to refresh the daemon, even though it will be used for all authorized users.
     * @param the IModelRpcProps to locate the opened iModel.
     */
    static async findOpenIModel(accessToken, iModel) {
        const iModelDb = IModelDb.findByKey(iModel.key);
        // call refreshContainer, just in case this is a V2 checkpoint whose sasToken is about to expire, or its default transaction is about to be restarted.
        await iModelDb.refreshContainerForRpc(accessToken);
        return iModelDb;
    }
    /**
     * Download and open a checkpoint or briefcase, ensuring the operation completes within a default timeout. If the time to open exceeds the timeout period,
     * a RpcPendingResponse exception is thrown
     */
    static async open(args) {
        const { activity, tokenProps, syncMode } = args;
        Logger.logTrace(loggerCategory, "RpcBriefcaseUtility.open", tokenProps);
        const timeout = args.timeout ?? 1000;
        if (syncMode === SyncMode.PullOnly || syncMode === SyncMode.PullAndPush) {
            const briefcaseDb = await BeDuration.race(timeout, this.openBriefcase(args));
            if (briefcaseDb === undefined) {
                Logger.logTrace(loggerCategory, "Open briefcase - pending", tokenProps);
                throw new RpcPendingResponse(); // eslint-disable-line @typescript-eslint/only-throw-error
            }
            // note: usage is logged in the function BriefcaseManager.downloadNewBriefcaseAndOpen
            return briefcaseDb;
        }
        if (!tokenProps.iModelId || !tokenProps.iTwinId || !tokenProps.changeset)
            throw new IModelError(IModelStatus.BadArg, "invalid arguments");
        const checkpoint = {
            iModelId: tokenProps.iModelId,
            iTwinId: tokenProps.iTwinId,
            changeset: tokenProps.changeset,
            accessToken: activity.accessToken,
        };
        // opening a checkpoint.
        let db;
        // first check if it's already open
        db = SnapshotDb.tryFindByKey(CheckpointManager.getKey(checkpoint));
        if (db) {
            Logger.logTrace(loggerCategory, "Checkpoint was already open", tokenProps);
            return db;
        }
        // now try V2 checkpoint
        db = await SnapshotDb.openCheckpointFromRpc(checkpoint);
        Logger.logTrace(loggerCategory, "using V2 checkpoint", tokenProps);
        return db;
    }
    static async openWithTimeout(activity, tokenProps, syncMode, timeout = 1000) {
        if (tokenProps.iModelId)
            await IModelHost.tileStorage?.initialize(tokenProps.iModelId);
        // eslint-disable-next-line @typescript-eslint/no-deprecated
        return (await this.open({ activity, tokenProps, syncMode, timeout })).toJSON();
    }
}
//# sourceMappingURL=RpcBriefcaseUtility.js.map