/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert, expect } from "chai";
import { join } from "path";
import { Guid } from "@itwin/core-bentley";
import { ChangesetType, LockState } from "@itwin/core-common";
import { BriefcaseManager } from "../../BriefcaseManager";
import { IModelHost } from "../../IModelHost";
import { IModelJsFs } from "../../IModelJsFs";
import { HubMock } from "../../internal/HubMock";
import { IModelTestUtils } from "../IModelTestUtils";
import { KnownTestLocations } from "../KnownTestLocations";
import { ProgressStatus } from "../../CheckpointManager";
import { _hubAccess } from "../../internal/Symbols";
describe("HubMock", () => {
    const tmpDir = join(KnownTestLocations.outputDir, "HubMockTest");
    const iTwinId = Guid.createValue();
    const version0 = IModelTestUtils.resolveAssetFile("test.bim");
    const accessToken = "fake token";
    before(() => {
        HubMock.startup("HubMockTest", KnownTestLocations.outputDir);
    });
    after(() => {
        HubMock.shutdown();
    });
    it("should be able to create HubMock", async () => {
        const iModelId = await IModelHost[_hubAccess].createNewIModel({ iTwinId, iModelName: "test imodel", version0 });
        const localHub = HubMock.findLocalHub(iModelId);
        let checkpoints = localHub.getCheckpoints();
        assert.equal(checkpoints.length, 1);
        assert.equal(checkpoints[0], 0);
        const cp1 = join(tmpDir, "cp-1.bim");
        localHub.downloadCheckpoint({ changeset: { index: 0 }, targetFile: cp1 });
        const stat1 = IModelJsFs.lstatSync(cp1);
        const statRev0 = IModelJsFs.lstatSync(version0);
        assert.equal(stat1?.size, statRev0?.size);
        assert.equal(2, localHub.acquireNewBriefcaseId("user1", "user1 briefcase 1"));
        assert.equal(3, localHub.acquireNewBriefcaseId("user2", "user2 briefcase 1"));
        assert.equal(4, localHub.acquireNewBriefcaseId("user3", "user3 briefcase 1"));
        let briefcases = localHub.getBriefcases();
        assert.equal(briefcases.length, 3);
        assert.deepEqual(briefcases[0], { id: 2, user: "user1", alias: "user1 briefcase 1", assigned: true });
        assert.deepEqual(briefcases[1], { id: 3, user: "user2", alias: "user2 briefcase 1", assigned: true });
        assert.deepEqual(briefcases[2], { id: 4, user: "user3", alias: "user3 briefcase 1", assigned: true });
        // releasing a briefcaseId should mark it as unassigned
        localHub.releaseBriefcaseId(2);
        briefcases = localHub.getBriefcases(false);
        assert.equal(briefcases.length, 3);
        assert.deepEqual(briefcases[0], { id: 2, user: "user1", alias: "user1 briefcase 1", assigned: false });
        assert.deepEqual(briefcases[1], { id: 3, user: "user2", alias: "user2 briefcase 1", assigned: true });
        assert.deepEqual(briefcases[2], { id: 4, user: "user3", alias: "user3 briefcase 1", assigned: true });
        localHub.releaseBriefcaseId(4);
        briefcases = localHub.getBriefcases();
        assert.equal(briefcases.length, 1);
        assert.deepEqual(briefcases[0], { id: 3, user: "user2", alias: "user2 briefcase 1", assigned: true });
        assert.equal(5, localHub.acquireNewBriefcaseId("user4"));
        briefcases = localHub.getBriefcases();
        assert.equal(briefcases.length, 2);
        assert.deepEqual(briefcases[0], { id: 3, user: "user2", alias: "user2 briefcase 1", assigned: true });
        assert.deepEqual(briefcases[1], { id: 5, user: "user4", alias: "user4 (5)", assigned: true });
        let pathname = IModelTestUtils.resolveAssetFile("CloneTest.01.00.00.ecschema.xml");
        let fileSize = IModelJsFs.lstatSync(pathname).size;
        // try pushing changesets
        const cs1 = {
            id: "changeset0", description: "first changeset", changesType: ChangesetType.Regular, parentId: "", briefcaseId: 5, pushDate: "", index: 0,
            userCreated: "user1", pathname, size: fileSize,
        };
        cs1.index = localHub.addChangeset(cs1); // first changeset
        const changesets1 = localHub.queryChangesets();
        assert.equal(changesets1.length, 1);
        assert.equal(changesets1[0].id, cs1.id);
        assert.equal(changesets1[0].description, cs1.description);
        assert.equal(changesets1[0].changesType, cs1.changesType);
        assert.equal(changesets1[0].index, 1);
        assert.equal(changesets1[0].briefcaseId, 5);
        assert.isAtLeast(changesets1[0].size, 1);
        assert.equal(changesets1[0].parentId, "");
        assert.isDefined(changesets1[0].pushDate);
        assert.equal(cs1.id, localHub.getLatestChangeset().id);
        pathname = IModelTestUtils.resolveAssetFile("CloneTest.01.00.01.ecschema.xml");
        fileSize = IModelJsFs.lstatSync(pathname).size;
        const cs2 = {
            id: "changeset1", parentId: cs1.id, description: "second changeset", changesType: ChangesetType.Schema, briefcaseId: 5, pushDate: "", index: 0,
            userCreated: "user2", pathname: IModelTestUtils.resolveAssetFile("CloneTest.01.00.01.ecschema.xml"),
            size: fileSize,
        };
        cs2.index = localHub.addChangeset(cs2); // second changeset, parent = cs1
        const changesets2 = localHub.queryChangesets();
        assert.equal(changesets2.length, 2);
        assert.deepEqual(changesets1[0], changesets2[0]);
        assert.equal(changesets2[1].id, cs2.id);
        assert.equal(changesets2[1].parentId, cs2.parentId);
        assert.equal(changesets2[1].description, cs2.description);
        assert.equal(changesets2[1].changesType, cs2.changesType);
        assert.equal(changesets2[1].index, 2);
        assert.equal(changesets2[1].briefcaseId, 5);
        assert.isAtLeast(changesets2[1].size, 1);
        assert.isDefined(changesets2[1].pushDate);
        assert.equal(cs2.id, localHub.getLatestChangeset().id);
        localHub.uploadCheckpoint({ changesetIndex: cs2.index, localFile: version0 });
        checkpoints = localHub.getCheckpoints();
        assert.equal(checkpoints.length, 2);
        assert.equal(checkpoints[1], 2);
        // test named versions
        const version1 = "release 1";
        const version2 = "release 2";
        localHub.addNamedVersion({ versionName: version1, csIndex: cs1.index });
        localHub.addNamedVersion({ versionName: version2, csIndex: cs2.index });
        assert.equal(localHub.findNamedVersion(version1).index, cs1.index);
        expect(() => localHub.findNamedVersion("not there")).throws("not found");
        expect(() => localHub.addNamedVersion({ versionName: version2, csIndex: cs2.index })).throws("insert");
        localHub.deleteNamedVersion(version1);
        expect(() => localHub.findNamedVersion(version1)).throws("not found");
        // test for duplicate changeset id fails
        const cs3 = {
            id: "changeset0", parentId: "changeset1",
            description: "third changeset", changesType: ChangesetType.Regular, pathname: cs1.pathname, briefcaseId: 500, userCreated: "", pushDate: "", index: 0,
            size: fileSize,
        };
        expect(() => localHub.addChangeset(cs3)).throws("no briefcase with that id");
        cs3.briefcaseId = 5;
        expect(() => localHub.addChangeset(cs3)).throws("can't insert");
        // now test for valid changeset id, but bad parentId
        const cs4 = { ...cs3, id: "changeset4", parentId: "bad", description: "fourth changeset" };
        expect(() => localHub.addChangeset(cs4)).throws("bad not found");
        cs3.id = "changeset3";
        cs3.parentId = cs2.id;
        cs3.index = localHub.addChangeset(cs3);
        assert.equal(0, localHub.queryPreviousCheckpoint(0));
        assert.equal(0, localHub.queryPreviousCheckpoint(cs1.index));
        assert.equal(cs2.index, localHub.queryPreviousCheckpoint(cs2.index));
        assert.equal(cs2.index, localHub.queryPreviousCheckpoint(cs3.index));
        // downloading changesets
        const cSets = localHub.downloadChangesets({ range: { first: cs1.index, end: cs2.index }, targetDir: tmpDir });
        assert.equal(cSets.length, 2);
        assert.equal(cSets[0].id, cs1.id);
        assert.equal(cSets[0].changesType, cs1.changesType);
        assert.equal(cSets[0].userCreated, cs1.userCreated);
        assert.equal(cSets[0].parentId, cs1.parentId);
        assert.equal(cSets[0].description, cs1.description);
        assert.equal(cSets[1].id, cs2.id);
        assert.equal(cSets[1].changesType, cs2.changesType);
        assert.equal(cSets[1].userCreated, cs2.userCreated);
        assert.equal(cSets[1].parentId, cs2.parentId);
        assert.equal(cSets[1].description, cs2.description);
        const orig1 = IModelJsFs.readFileSync(cs1.pathname);
        const downloaded1 = IModelJsFs.readFileSync(cSets[0].pathname);
        assert.deepEqual(orig1, downloaded1);
        const orig2 = IModelJsFs.readFileSync(cs2.pathname);
        const downloaded2 = IModelJsFs.readFileSync(cSets[1].pathname);
        assert.deepEqual(orig2, downloaded2);
        assert.notDeepEqual(orig1, orig2);
        const latest = await BriefcaseManager.getLatestChangeset({ iModelId });
        expect(latest.index).to.equal(cs3.index);
        expect(latest.id).to.equal(cs3.id);
        expect(latest.parentId).to.equal(cs3.parentId);
        const cs1q = await BriefcaseManager.queryChangeset({ changeset: { index: cs1.index }, iModelId });
        expect(cs1q.description).to.equal(cs1.description);
        const changes = await BriefcaseManager.queryChangesets({ range: { first: cs1.index }, iModelId });
        expect(changes.length).to.equal(3);
        // test locks
        const lock1 = {
            state: LockState.Shared,
            id: "0x12",
        };
        // get a new briefcaseId for some locks
        assert.equal(6, localHub.acquireNewBriefcaseId("user5", "alias for 5"));
        localHub.acquireLock(lock1, { briefcaseId: 3, changeset: cs1 });
        assert.equal(localHub.countSharedLocks(), 1);
        assert.equal(localHub.countLocks(), 1);
        let lockStat = localHub.queryLockStatus(lock1.id);
        assert.equal(lockStat.state, LockState.Shared);
        assert.equal(lockStat.sharedBy.size, 1);
        assert.isTrue(lockStat.sharedBy.has(3));
        assert.isUndefined(lockStat.lastCsIndex);
        localHub.acquireLock(lock1, { briefcaseId: 5, changeset: cs1 });
        assert.equal(localHub.countSharedLocks(), 2);
        assert.equal(localHub.countLocks(), 1);
        lockStat = localHub.queryLockStatus(lock1.id);
        assert.equal(lockStat.sharedBy.size, 2);
        assert.isTrue(lockStat.sharedBy.has(3));
        assert.isTrue(lockStat.sharedBy.has(5));
        expect(() => localHub.acquireLock({ ...lock1, state: LockState.Exclusive }, { briefcaseId: 6, changeset: { id: "cs1" } })).to.throw("shared lock is held").include({ briefcaseId: 3, briefcaseAlias: "user2 briefcase 1" });
        expect(() => localHub.releaseLocks([lock1], { briefcaseId: 9, changesetIndex: cs1.index })).to.throw("shared lock not held");
        localHub.releaseLocks([lock1], { briefcaseId: 3, changesetIndex: cs1.index });
        assert.equal(localHub.countSharedLocks(), 1);
        assert.equal(localHub.countLocks(), 1);
        lockStat = localHub.queryLockStatus(lock1.id);
        assert.equal(lockStat.sharedBy.size, 1);
        localHub.releaseLocks([lock1], { briefcaseId: 5, changesetIndex: cs1.index });
        assert.equal(localHub.countSharedLocks(), 0);
        assert.equal(localHub.countLocks(), 1);
        lockStat = localHub.queryLockStatus(lock1.id);
        assert.equal(lockStat.state, LockState.None);
        lock1.state = LockState.Exclusive;
        localHub.acquireLock(lock1, { briefcaseId: 6, changeset: cs1 });
        lockStat = localHub.queryLockStatus(lock1.id);
        assert.equal(lockStat.state, LockState.Exclusive);
        localHub.acquireLock(lock1, { briefcaseId: 6, changeset: cs1 });
        assert.equal(localHub.countSharedLocks(), 0);
        assert.equal(localHub.countLocks(), 1);
        expect(() => localHub.acquireLock(lock1, { briefcaseId: 5, changeset: cs1 })).to.throw("exclusive lock is already held").include({ briefcaseId: 6, briefcaseAlias: "alias for 5" });
        expect(() => localHub.acquireLock({ ...lock1, state: LockState.Shared }, { briefcaseId: 5, changeset: cs1 })).to.throw("exclusive lock is already held").include({ briefcaseId: 6, briefcaseAlias: "alias for 5" });
        localHub.releaseLocks([lock1], { briefcaseId: 6, changesetIndex: cs2.index });
        assert.equal(localHub.countLocks(), 1);
        lockStat = localHub.queryLockStatus(lock1.id);
        assert.equal(lockStat.state, LockState.None);
        assert.equal(lockStat.lastCsIndex, cs2.index);
        expect(() => localHub.acquireLock(lock1, { briefcaseId: 5, changeset: cs1 })).to.throw("pull is required");
        localHub.acquireLock(lock1, { briefcaseId: 5, changeset: cs2 });
        lockStat = localHub.queryLockStatus(lock1.id);
        assert.equal(lockStat.state, LockState.Exclusive);
        assert.equal(lockStat.briefcaseId, 5);
        assert.equal(lockStat.lastCsIndex, cs2.index);
        localHub.acquireLock({ state: LockState.Exclusive, id: "0x22" }, { briefcaseId: 5, changeset: cs1 });
        lockStat = localHub.queryLockStatus("0x22");
        assert.equal(lockStat.state, LockState.Exclusive);
        assert.equal(lockStat.briefcaseId, 5);
        assert.isUndefined(lockStat.lastCsIndex);
        localHub.acquireLock({ state: LockState.Exclusive, id: "0x23" }, { briefcaseId: 6, changeset: cs1 });
        localHub.acquireLock({ state: LockState.Shared, id: "0x24" }, { briefcaseId: 6, changeset: cs1 });
        localHub.acquireLock({ state: LockState.Shared, id: "0x24" }, { briefcaseId: 5, changeset: cs1 });
        let locks = localHub.queryAllLocks(5);
        assert.equal(locks.length, 3);
        localHub.releaseBriefcaseId(5); // releasing a briefcaseId with held locks should release them
        lockStat = localHub.queryLockStatus("0x22");
        locks = localHub.queryAllLocks(5);
        assert.equal(locks.length, 0);
        assert.equal(localHub.countSharedLocks(), 1);
        localHub.releaseAllLocks({ briefcaseId: 6, changesetIndex: 3 });
        assert.equal(localHub.countSharedLocks(), 0);
        assert.equal(localHub.countLocks(), 4);
        lockStat = localHub.queryLockStatus("0x23");
        assert.equal(lockStat.lastCsIndex, 3);
        assert.equal(lockStat.state, 0);
        lockStat = localHub.queryLockStatus("0x24");
        assert.equal(lockStat.lastCsIndex, undefined);
        assert.equal(lockStat.state, 0);
        await IModelHost[_hubAccess].deleteIModel({ iTwinId, iModelId });
    });
    it("HubMock report progress of changesets 'downloads'", async () => {
        const iModelId = await IModelHost[_hubAccess].createNewIModel({ iTwinId, iModelName: "test imodel", version0 });
        const localHub = HubMock.findLocalHub(iModelId);
        const briefcaseId = await HubMock.acquireNewBriefcaseId({ iModelId });
        const pathname = IModelTestUtils.resolveAssetFile("CloneTest.01.00.00.ecschema.xml");
        const fileSize = IModelJsFs.lstatSync(pathname).size;
        const cs1 = {
            id: "changeset0", description: "first changeset", changesType: ChangesetType.Regular, parentId: "", briefcaseId, pushDate: "", index: 0,
            size: fileSize,
            userCreated: "user1", pathname,
        };
        const cs2 = {
            id: "changeset1", parentId: cs1.id, description: "second changeset", changesType: ChangesetType.Schema, briefcaseId, pushDate: "", index: 0,
            size: fileSize,
            userCreated: "user2", pathname: IModelTestUtils.resolveAssetFile("CloneTest.01.00.01.ecschema.xml"),
        };
        cs1.index = localHub.addChangeset(cs1);
        cs2.index = localHub.addChangeset(cs2);
        // Progress reporting of single changeset "download"
        let progressReports = [];
        let progressCallback = (downloaded, total) => {
            progressReports.push({ downloaded, total });
            return ProgressStatus.Continue;
        };
        let cProps = await HubMock.downloadChangeset({
            iModelId,
            changeset: {
                index: cs1.index,
            },
            targetDir: tmpDir,
            progressCallback,
        });
        let previousReport = { downloaded: 0, total: 0 };
        for (const report of progressReports) {
            assert.isTrue(report.downloaded > previousReport.downloaded);
            assert.equal(report.total, cProps.size);
        }
        // Progress reporting of multiple changesets "download"
        progressReports = [];
        let cPropsSet = await HubMock.downloadChangesets({
            iModelId,
            targetDir: tmpDir,
            progressCallback,
        });
        previousReport = { downloaded: 0, total: 0 };
        const totalSize = cPropsSet.reduce((sum, props) => sum + (props.size ?? 0), 0);
        for (const report of progressReports) {
            assert.isTrue(report.downloaded > previousReport.downloaded);
            assert.equal(report.total, totalSize);
        }
        // Cancel single changeset "download"
        progressReports = [];
        progressCallback = (downloaded, total) => {
            progressReports.push({ downloaded, total });
            return downloaded > total / 2 ? ProgressStatus.Abort : ProgressStatus.Continue;
        };
        let errorThrown = false;
        try {
            cProps = await HubMock.downloadChangeset({
                iModelId,
                changeset: {
                    index: cs1.index,
                },
                targetDir: tmpDir,
                progressCallback,
            });
        }
        catch {
            errorThrown = true;
        }
        assert.isTrue(errorThrown);
        let lastReport = progressReports[progressReports.length - 1];
        assert.isBelow(lastReport.downloaded, lastReport.total);
        // Cancel multiple changesets "download"
        progressReports = [];
        errorThrown = false;
        try {
            cPropsSet = await HubMock.downloadChangesets({
                iModelId,
                targetDir: tmpDir,
                progressCallback,
            });
        }
        catch {
            errorThrown = true;
        }
        assert.isTrue(errorThrown);
        lastReport = progressReports[progressReports.length - 1];
        assert.isBelow(lastReport.downloaded, lastReport.total);
    });
    it("use HubMock with BriefcaseManager", async () => {
        const iModelId = await IModelHost[_hubAccess].createNewIModel({ iTwinId, iModelName: "test imodel", version0 });
        const briefcase = await BriefcaseManager.downloadBriefcase({ accessToken, iTwinId, iModelId });
        assert.equal(briefcase.briefcaseId, 2);
        assert.equal(briefcase.changeset.id, "");
        assert.equal(briefcase.iModelId, iModelId);
        assert.equal(briefcase.iTwinId, iTwinId);
        await IModelHost[_hubAccess].deleteIModel({ iTwinId, iModelId });
    });
});
//# sourceMappingURL=HubMock.test.js.map