import "./packages/tenants/src/index.mjs";
import { AskarToDrizzleStorageMigrationError } from "./errors/AskarToDrizzleStorageMigrationError.mjs";
import { Agent, AgentContext, BaseRecord, ConsoleLogger, LogLevel, StorageVersionRecord } from "@credo-ts/core";
import { EntryList } from "@openwallet-foundation/askar-shared";
import { AskarModule, AskarModuleConfig, AskarStoreManager, recordToInstance } from "@credo-ts/askar";
import { DrizzleStorageModule, DrizzleStorageModuleConfig } from "@credo-ts/drizzle-storage";

//#region src/AskarToDrizzleStorageMigrator.ts
/**
* Migration class to move storage from Askar to Drizzle. Note that this does not migrate
* the KMS part of Askar (the keys), and only the normal record storage.
*
* No backups are created during the process. The askar store is only used for reading, the
* drizzle store is also used for writing. Ensure the drizzle target is empty when starting the
* migration.
*
* Note that if you were previously using `AskarMultiWalletDatabaseScheme.DatabasePerWallet`, that all storage will be moved
* to a single database. You can still keep using multiiple databases for the Askar storage, but Drizzle only supports
* a single database per main agent instance (including sub agents for tenants).
*/
var AskarToDrizzleStorageMigrator = class AskarToDrizzleStorageMigrator {
	constructor(drizzleModule, askarModule, agentDependencies, logger, skipMigrationforMissingAdapter, tenantsModule) {
		this.drizzleModule = drizzleModule;
		this.askarModule = askarModule;
		this.agentDependencies = agentDependencies;
		this.logger = logger;
		this.skipMigrationforMissingAdapter = skipMigrationforMissingAdapter;
		this.tenantsModule = tenantsModule;
		this.drizzleAgent = new Agent({
			dependencies: this.agentDependencies,
			config: { logger: this.logger },
			modules: this.tenantsModule ? {
				drizzle: this.drizzleModule,
				tenants: this.tenantsModule
			} : { drizzle: this.drizzleModule }
		});
		this.askarAgent = new Agent({
			modules: this.tenantsModule ? {
				askar: this.askarModule,
				tenants: this.tenantsModule
			} : { askar: this.askarModule },
			config: { logger: this.logger },
			dependencies: this.agentDependencies
		});
		this.drizzleConfig = this.drizzleAgent.context.resolve(DrizzleStorageModuleConfig);
		this.drizzleAdapters = this.drizzleConfig.adapters;
	}
	static async initialize({ drizzleModule, askarModule, agentDependencies, logger = new ConsoleLogger(LogLevel.trace), skipMigrationForMissingAdapter = false, tenantsModule }) {
		if (!askarModule.config.enableStorage) throw new AskarToDrizzleStorageMigrationError("Askar module has enableStorage set to false. Make sure the storage is enabled on the askar module");
		return new AskarToDrizzleStorageMigrator(drizzleModule, askarModule, agentDependencies, logger, skipMigrationForMissingAdapter, tenantsModule);
	}
	getAdapterForRecordType(recordType) {
		const adapter = this.drizzleAdapters.find((adapter$1) => adapter$1.recordClass.type === recordType);
		if (!adapter) return null;
		return adapter;
	}
	async migrate() {
		try {
			await this.drizzleAgent.initialize();
			this.logger.info("Successfully initialized drizzle agent");
			await this.askarAgent.initialize();
			this.logger.info("Successfully initialized askar agent");
			this.logger.info("Starting migration of default agent context");
			await this.migrateForContext({
				askarContext: this.askarAgent.context,
				drizzleContext: this.drizzleAgent.context
			});
			this.logger.info("Succesfully migrated default agent context");
			if (this.tenantsModule) {
				const askarAgentWithTenants = this.askarAgent;
				const drizzleAgentWithTenants = this.drizzleAgent;
				this.logger.info("Detected tenants module, migrating tenants context");
				const allTenants = await askarAgentWithTenants.modules.tenants.getAllTenants();
				this.logger.debug(`Retrieved '${allTenants.length}' tenant records to migrate.`);
				for (const tenant of allTenants) {
					this.logger.info(`Starting migration of tenant '${tenant.id}'`);
					await askarAgentWithTenants.modules.tenants.withTenantAgent({ tenantId: tenant.id }, async (askarTenantAgent) => {
						await drizzleAgentWithTenants.modules.tenants.withTenantAgent({ tenantId: tenant.id }, (drizzleTenantAgent) => this.migrateForContext({
							askarContext: askarTenantAgent.context,
							drizzleContext: drizzleTenantAgent.context
						}));
					});
					this.logger.info(`Succesfully migrated tenant '${tenant.id}'`);
				}
			}
		} finally {
			if (this.drizzleAgent.isInitialized) await this.drizzleAgent.shutdown();
			if (this.askarAgent.isInitialized) await this.askarAgent.shutdown();
		}
	}
	async migrateForContext({ drizzleContext, askarContext }) {
		try {
			const storeManager = askarContext.resolve(AskarStoreManager);
			const askar = askarContext.resolve(AskarModuleConfig).askar;
			const { store, profile } = await storeManager.getInitializedStoreWithProfile(askarContext);
			await this.drizzleModule.onProvisionContext(drizzleContext);
			let scanHandle = void 0;
			let entryListHandle = null;
			let migratedRecordCount = 0;
			let skippedRecordCount = 0;
			const skippedRecordTypes = [];
			try {
				this.logger.debug(`Initializing retrieval of records from askar storage for context '${askarContext.contextCorrelationId}'`);
				scanHandle = await askar.scanStart({
					storeHandle: store.handle,
					profile
				});
				while (true) {
					this.logger.debug(`Fetching batch of records from askar storage for context '${askarContext.contextCorrelationId}'`);
					entryListHandle = await askar.scanNext({ scanHandle });
					if (!entryListHandle) {
						this.logger.debug(`No records returned in batch from askar storage. No records left to migrate for context '${askarContext.contextCorrelationId}'`);
						break;
					}
					const list = new EntryList({ handle: entryListHandle });
					this.logger.debug(`Retrieved batch of '${list.length}' records from askar storage for context '${askarContext.contextCorrelationId}'`);
					for (let index = 0; index < list.length; index++) {
						const entry = list.getEntryByIndex(index);
						const entryObject = entry.toJson();
						this.logger.trace(`Retrieved record with index '${index}', id '${entryObject.name}' and type '${entryObject.category}' from batch for context '${askarContext.contextCorrelationId}'`);
						const adapter = this.getAdapterForRecordType(entryObject.category);
						if (!adapter) {
							if (this.skipMigrationforMissingAdapter) {
								this.logger.info(`Skippping migration of record '${entry.name}' with type '${entry.category}' due to missing drizzle adapter for context '${askarContext.contextCorrelationId}'.`);
								skippedRecordCount++;
								skippedRecordTypes.push(entryObject.category);
								continue;
							}
							throw new AskarToDrizzleStorageMigrationError(`Could not find a registered drizzle adapter for record type '${entry.category}'. Make sure to register the record type in the DrizzleStorageModule.`);
						}
						const record = recordToInstance(entryObject, adapter.recordClass);
						if (record instanceof StorageVersionRecord && record.id === StorageVersionRecord.storageVersionRecordId) {
							this.logger.debug(`Updating record '${record.id}' with type '${record.type}' into drizzle storage for context '${askarContext.contextCorrelationId}'`);
							await adapter.update(drizzleContext, record);
						} else {
							this.logger.debug(`Inserting record '${record.id}' with type '${record.type}' into drizzle storage for context '${askarContext.contextCorrelationId}'`);
							await adapter.insert(drizzleContext, record);
						}
						migratedRecordCount++;
					}
					this.logger.debug(`Processed all records from batch for context '${askarContext.contextCorrelationId}'`);
					entryListHandle.free();
					entryListHandle = null;
				}
				this.logger.debug(`Succesfully migrated '${migratedRecordCount}' records from Askar to Drizzle for context '${askarContext.contextCorrelationId}'`, {
					migratedRecordCount,
					skippedRecordCount,
					skippedRecordTypes
				});
			} finally {
				entryListHandle?.free();
				entryListHandle = null;
				scanHandle?.free();
				scanHandle = void 0;
			}
		} catch (error) {
			this.logger.error(`Migration failed for context '${askarContext.contextCorrelationId}'. ${error.message}`);
			throw new AskarToDrizzleStorageMigrationError(`Migration failed for context '${askarContext.contextCorrelationId}'. ${error.message}`, { cause: error });
		}
	}
};

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