import { TenantAgent } from "../TenantAgent.mjs";
import { __decorateMetadata } from "../../../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorateMetadata.mjs";
import { __decorate } from "../../../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorate.mjs";
import { isValidJweStructure } from "../../../didcomm/src/util/JWE.mjs";
import { __decorateParam } from "../../../../_virtual/_@oxc-project_runtime@0.94.0/helpers/decorateParam.mjs";
import { DidCommRoutingEventTypes } from "../../../didcomm/src/modules/routing/DidCommRoutingEvents.mjs";
import "../../../didcomm/src/index.mjs";
import { TenantRecordService } from "../services/TenantRecordService.mjs";
import "../services/index.mjs";
import { TenantSessionCoordinator } from "./TenantSessionCoordinator.mjs";
import { AgentContext, CredoError, EventEmitter, InjectionSymbols, JsonEncoder, Kms, TypedArrayEncoder, UpdateAssistant, inject, injectable, isJsonObject, isStorageUpToDate } from "@credo-ts/core";

//#region ../tenants/src/context/TenantAgentContextProvider.ts
var _ref, _ref2, _ref3, _ref4;
let TenantAgentContextProvider = class TenantAgentContextProvider$1 {
	constructor(tenantRecordService, rootAgentContext, eventEmitter, tenantSessionCoordinator, logger) {
		this.tenantRecordService = tenantRecordService;
		this.rootAgentContext = rootAgentContext;
		this.eventEmitter = eventEmitter;
		this.tenantSessionCoordinator = tenantSessionCoordinator;
		this.logger = logger;
		this.listenForRoutingKeyCreatedEvents();
	}
	getContextCorrelationIdForTenantId(tenantId) {
		return this.tenantSessionCoordinator.getContextCorrelationIdForTenantId(tenantId);
	}
	async getAgentContextForContextCorrelationId(contextCorrelationId, { provisionContext = false } = {}) {
		if (contextCorrelationId === this.rootAgentContext.contextCorrelationId) return this.rootAgentContext;
		this.tenantSessionCoordinator.assertTenantContextCorrelationId(contextCorrelationId);
		const tenantId = this.tenantSessionCoordinator.getTenantIdForContextCorrelationId(contextCorrelationId);
		const tenantRecord = await this.tenantRecordService.getTenantById(this.rootAgentContext, tenantId);
		const shouldUpdate = !isStorageUpToDate(tenantRecord.storageVersion);
		if (shouldUpdate && !this.rootAgentContext.config.autoUpdateStorageOnStartup) throw new CredoError(`Current agent storage for tenant ${tenantRecord.id} is not up to date. To prevent the tenant state from getting corrupted the tenant initialization is aborted. Make sure to update the tenant storage (currently at ${tenantRecord.storageVersion}) to the latest version (${UpdateAssistant.frameworkStorageVersion}). You can also downgrade your version of Credo.`);
		const agentContext = await this.tenantSessionCoordinator.getContextForSession(tenantRecord, {
			provisionContext,
			runInMutex: shouldUpdate ? (agentContext$1) => this._updateTenantStorage(tenantRecord, agentContext$1) : void 0
		});
		this.logger.debug(`Created tenant agent context for context correlation id '${contextCorrelationId}'`);
		return agentContext;
	}
	async getContextForInboundMessage(inboundMessage, options) {
		this.logger.debug("Getting context for inbound message in tenant agent context provider", { contextCorrelationId: options?.contextCorrelationId });
		let tenantId = options?.contextCorrelationId && this.tenantSessionCoordinator.isTenantContextCorrelationId(options.contextCorrelationId) ? this.tenantSessionCoordinator.getTenantIdForContextCorrelationId(options.contextCorrelationId) : void 0;
		let recipientKeys = [];
		if (!tenantId && isValidJweStructure(inboundMessage)) {
			this.logger.trace("Inbound message is a JWE, extracting tenant id from JWE's protected header");
			recipientKeys = this.getRecipientKeysFromEncryptedMessage(inboundMessage);
			this.logger.trace(`Found ${recipientKeys.length} recipient keys in JWE's protected header`);
			for (const recipientKey of recipientKeys) {
				const tenantRoutingRecord = await this.tenantRecordService.findTenantRoutingRecordByRecipientKey(this.rootAgentContext, recipientKey);
				if (tenantRoutingRecord) {
					this.logger.debug(`Found tenant routing record for recipient key ${recipientKeys[0].fingerprint}`, { tenantId: tenantRoutingRecord.tenantId });
					tenantId = tenantRoutingRecord.tenantId;
					break;
				}
			}
		}
		if (!tenantId) {
			this.logger.error("Couldn't determine tenant id for inbound message. Unable to create context", {
				inboundMessage,
				recipientKeys: recipientKeys.map((key) => key.fingerprint)
			});
			throw new CredoError("Couldn't determine tenant id for inbound message. Unable to create context");
		}
		const contextCorrelationId = this.tenantSessionCoordinator.getContextCorrelationIdForTenantId(tenantId);
		return await this.getAgentContextForContextCorrelationId(contextCorrelationId);
	}
	async endSessionForAgentContext(agentContext) {
		await this.tenantSessionCoordinator.endAgentContextSession(agentContext);
	}
	async deleteAgentContext(agentContext) {
		await this.tenantSessionCoordinator.deleteAgentContext(agentContext);
	}
	getRecipientKeysFromEncryptedMessage(jwe) {
		const jweProtected = JsonEncoder.fromBase64(jwe.protected);
		if (!Array.isArray(jweProtected.recipients)) return [];
		const recipientKeys = [];
		for (const recipient of jweProtected.recipients) if (isJsonObject(recipient) && isJsonObject(recipient.header) && typeof recipient.header.kid === "string") {
			const publicJwk = Kms.PublicJwk.fromPublicKey({
				crv: "Ed25519",
				kty: "OKP",
				publicKey: TypedArrayEncoder.fromBase58(recipient.header.kid)
			});
			recipientKeys.push(publicJwk);
		}
		return recipientKeys;
	}
	async registerRecipientKeyForTenant(tenantId, recipientKey) {
		this.logger.debug(`Registering recipient key ${recipientKey.fingerprint} for tenant ${tenantId}`);
		const tenantRecord = await this.tenantRecordService.getTenantById(this.rootAgentContext, tenantId);
		await this.tenantRecordService.addTenantRoutingRecord(this.rootAgentContext, tenantRecord.id, recipientKey);
	}
	listenForRoutingKeyCreatedEvents() {
		this.logger.debug("Listening for routing key created events in tenant agent context provider");
		this.eventEmitter.on(DidCommRoutingEventTypes.RoutingCreatedEvent, async (event) => {
			const contextCorrelationId = event.metadata.contextCorrelationId;
			const recipientKey = event.payload.routing.recipientKey;
			if (contextCorrelationId === this.rootAgentContext.contextCorrelationId) return;
			this.tenantSessionCoordinator.assertTenantContextCorrelationId(contextCorrelationId);
			this.logger.debug(`Received routing key created event for tenant context ${contextCorrelationId}, registering recipient key ${recipientKey.fingerprint} in base wallet`);
			await this.registerRecipientKeyForTenant(this.tenantSessionCoordinator.getTenantIdForContextCorrelationId(contextCorrelationId), recipientKey);
		});
	}
	/**
	* Method to allow updating the tenant storage, this method can be called from the TenantsApi
	* to update the storage for a tenant manually
	*/
	async updateTenantStorage(tenantRecord, updateOptions) {
		await (await this.tenantSessionCoordinator.getContextForSession(tenantRecord, { runInMutex: (agentContext) => this._updateTenantStorage(tenantRecord, agentContext, updateOptions) })).endSession();
	}
	/**
	* Handle the case where the tenant storage is outdated. If auto-update is disabled we will throw an error
	* and not update the storage. If auto-update is enabled we will update the storage.
	*
	* When this method is called we can be sure that we are in the mutex runExclusive lock and thus other sessions
	* will not be able to open a session for this tenant until we're done.
	*
	* NOTE: We don't support multi-instance locking for now. That means you can only have a single instance open and
	* it will prevent multiple processes from updating the tenant storage at the same time. However if multi-instances
	* are used, we can't prevent multiple instances from updating the tenant storage at the same time.
	* In the future we can make the tenantSessionCoordinator an interface and allowing a instance-tenant-lock as well
	* as an tenant-lock (across all instances)
	*/
	async _updateTenantStorage(tenantRecord, agentContext, updateOptions) {
		try {
			const updateAssistant = new UpdateAssistant(new TenantAgent(agentContext));
			await updateAssistant.initialize();
			await updateAssistant.update(updateOptions);
			tenantRecord.storageVersion = await updateAssistant.getCurrentAgentStorageVersion();
			await this.rootAgentContext.dependencyManager.resolve(TenantRecordService).updateTenant(this.rootAgentContext, tenantRecord);
		} catch (error) {
			this.logger.error(`Error occurred while updating tenant storage for tenant ${tenantRecord.id}`, error);
			throw error;
		}
	}
};
TenantAgentContextProvider = __decorate([
	injectable(),
	__decorateParam(4, inject(InjectionSymbols.Logger)),
	__decorateMetadata("design:paramtypes", [
		typeof (_ref = typeof TenantRecordService !== "undefined" && TenantRecordService) === "function" ? _ref : Object,
		typeof (_ref2 = typeof AgentContext !== "undefined" && AgentContext) === "function" ? _ref2 : Object,
		typeof (_ref3 = typeof EventEmitter !== "undefined" && EventEmitter) === "function" ? _ref3 : Object,
		typeof (_ref4 = typeof TenantSessionCoordinator !== "undefined" && TenantSessionCoordinator) === "function" ? _ref4 : Object,
		Object
	])
], TenantAgentContextProvider);

//#endregion
export { TenantAgentContextProvider };
//# sourceMappingURL=TenantAgentContextProvider.mjs.map