/**
 * SmartDB/SmartData manager for Onebox.
 *
 * Backend code works with SmartData document instances directly. DTO/plain-object
 * serialization belongs at API boundaries, where doc.toJSON()/doc.toData() is used.
 */

import * as plugins from '../plugins.js';
import type {
  IBackup,
  IBackupSchedule,
  IBackupScheduleUpdate,
  ICertificate,
  ICertRequirement,
  IDomain,
  IHostedAppLifecycleRecord,
  IHostedAppLifecycleState,
  ILogEntry,
  IMetric,
  IPasskeyCredential,
  IPlatformResource,
  IPlatformService,
  IRegistry,
  IRegistryToken,
  IService,
  IServiceVolume,
  ISslCertificate,
  IStorageClass,
  IStorageTarget,
  IUser,
  TPlatformServiceType,
  TStorageClassPurpose,
} from '../types.js';
import { logger } from '../logging.js';
import { getErrorMessage } from '../utils/error.js';
import { SecretSettingsManager } from './secret-settings.js';
import { ServiceSecretsManager } from './service-secrets.js';
import {
  BackupDoc,
  BackupScheduleDoc,
  CertificateDoc,
  CertRequirementDoc,
  DnsRecordDoc,
  DomainDoc,
  HostedAppLifecycleDoc,
  LogEntryDoc,
  MetadataDoc,
  MetricDoc,
  PlatformResourceDoc,
  PlatformServiceDoc,
  RegistryDoc,
  RegistryTokenDoc,
  SecretSettingDoc,
  ServiceSecretBundleDoc,
  ServiceDoc,
  SettingDoc,
  StorageClassDoc,
  StorageTargetDoc,
  type TDnsRecordWithZone,
  UserDoc,
} from './docs.js';
import { runStartupMigrations } from '../../ts_migration/index.js';

type TNumberedRecord = { id?: number };
type TKeyedRecord = { key: string };
type TSmartDataRuntimeDoc = {
  save(): Promise<unknown>;
  delete(): Promise<unknown>;
};
type TSmartDataDocClass<TDoc> = {
  new (dataArg?: Partial<any>): TDoc;
  getInstances(filterArg: Record<string, unknown>): Promise<TDoc[]>;
  getInstance(filterArg: Record<string, unknown>): Promise<TDoc | null>;
};

const smartDataRuntimeDoc = (docArg: object): TSmartDataRuntimeDoc =>
  docArg as unknown as TSmartDataRuntimeDoc;

const applyDefinedUpdates = (
  targetArg: Record<string, unknown>,
  updatesArg: Record<string, unknown>,
): void => {
  for (const [key, value] of Object.entries(updatesArg)) {
    if (value !== undefined) {
      targetArg[key] = value;
    }
  }
};

const base64urlPattern = /^[A-Za-z0-9_-]+$/;
const passkeyTransportValues = new Set([
  'ble',
  'cable',
  'hybrid',
  'internal',
  'nfc',
  'smart-card',
  'usb',
]);

const requireSelectorString = (
  valueArg: unknown,
  labelArg: string,
): string => {
  if (typeof valueArg !== 'string' || valueArg.trim().length === 0) {
    throw new Error(`${labelArg} is missing or malformed`);
  }
  return valueArg;
};

const requireBase64urlString = (
  valueArg: unknown,
  labelArg: string,
): string => {
  const value = requireSelectorString(valueArg, labelArg);
  if (!base64urlPattern.test(value)) {
    throw new Error(`${labelArg} must be base64url without padding`);
  }
  const decodedValue = Buffer.from(value, 'base64url');
  if (decodedValue.length === 0 || decodedValue.toString('base64url') !== value) {
    throw new Error(`${labelArg} must be canonical nonempty base64url`);
  }
  return value;
};

const requireNonNegativeInteger = (
  valueArg: unknown,
  labelArg: string,
): number => {
  if (!Number.isSafeInteger(valueArg) || (valueArg as number) < 0) {
    throw new Error(`${labelArg} must be a non-negative safe integer`);
  }
  return valueArg as number;
};

const normalizePasskeyCredential = (
  credentialArg: IPasskeyCredential,
): IPasskeyCredential => {
  if (
    !credentialArg
    || typeof credentialArg !== 'object'
  ) {
    throw new Error('passkey credential is missing or malformed');
  }

  const credentialId = requireBase64urlString(credentialArg.credentialId, 'credentialId');
  const publicKey = requireBase64urlString(credentialArg.publicKey, 'publicKey');
  const counter = requireNonNegativeInteger(credentialArg.counter, 'counter');
  const rpId = requireSelectorString(credentialArg.rpId, 'rpId');
  const origin = requireSelectorString(credentialArg.origin, 'origin');
  const createdAt = requireNonNegativeInteger(credentialArg.createdAt, 'createdAt');
  const lastUsedAt = credentialArg.lastUsedAt === null
    ? null
    : requireNonNegativeInteger(credentialArg.lastUsedAt, 'lastUsedAt');

  if (
    credentialArg.deviceType !== 'singleDevice'
    && credentialArg.deviceType !== 'multiDevice'
  ) {
    throw new Error('deviceType is missing or malformed');
  }
  if (typeof credentialArg.backedUp !== 'boolean') {
    throw new Error('backedUp is missing or malformed');
  }

  const transportsInput = credentialArg.transports;
  let transports: IPasskeyCredential['transports'];
  if (transportsInput === undefined || transportsInput === null) {
    transports = null;
  } else {
    if (!Array.isArray(transportsInput)) {
      throw new Error('transports is malformed');
    }
    transports = transportsInput.map((transportArg) => {
      if (typeof transportArg !== 'string' || !passkeyTransportValues.has(transportArg)) {
        throw new Error('transports contains a malformed value');
      }
      return transportArg;
    }) as NonNullable<IPasskeyCredential['transports']>;
  }

  return {
    credentialId,
    publicKey,
    counter,
    transports,
    deviceType: credentialArg.deviceType,
    backedUp: credentialArg.backedUp,
    rpId,
    origin,
    createdAt,
    lastUsedAt,
  };
};

export class OneboxDatabase {
  private localSmartDb: plugins.smartdb.LocalSmartDb | null = null;
  private smartdataDb: plugins.smartdata.SmartdataDb | null = null;
  private smartDbPath: string;

  public CMetadata = plugins.smartdata.setDefaultManagerForDoc(this, MetadataDoc) as unknown as
    & TSmartDataDocClass<MetadataDoc>
    & typeof MetadataDoc;
  public CService = plugins.smartdata.setDefaultManagerForDoc(this, ServiceDoc) as unknown as
    & TSmartDataDocClass<ServiceDoc>
    & typeof ServiceDoc;
  public CServiceSecretBundle = plugins.smartdata.setDefaultManagerForDoc(
    this,
    ServiceSecretBundleDoc,
  ) as unknown as TSmartDataDocClass<ServiceSecretBundleDoc> & typeof ServiceSecretBundleDoc;
  public CHostedAppLifecycle = plugins.smartdata.setDefaultManagerForDoc(
    this,
    HostedAppLifecycleDoc,
  ) as unknown as TSmartDataDocClass<HostedAppLifecycleDoc> & typeof HostedAppLifecycleDoc;
  public CRegistry = plugins.smartdata.setDefaultManagerForDoc(this, RegistryDoc) as unknown as
    & TSmartDataDocClass<RegistryDoc>
    & typeof RegistryDoc;
  public CRegistryToken = plugins.smartdata.setDefaultManagerForDoc(
    this,
    RegistryTokenDoc,
  ) as unknown as TSmartDataDocClass<RegistryTokenDoc> & typeof RegistryTokenDoc;
  public CStorageTarget = plugins.smartdata.setDefaultManagerForDoc(
    this,
    StorageTargetDoc,
  ) as unknown as TSmartDataDocClass<StorageTargetDoc> & typeof StorageTargetDoc;
  public CStorageClass = plugins.smartdata.setDefaultManagerForDoc(
    this,
    StorageClassDoc,
  ) as unknown as TSmartDataDocClass<StorageClassDoc> & typeof StorageClassDoc;
  public CSetting = plugins.smartdata.setDefaultManagerForDoc(this, SettingDoc) as unknown as
    & TSmartDataDocClass<SettingDoc>
    & typeof SettingDoc;
  public CSecretSetting = plugins.smartdata.setDefaultManagerForDoc(
    this,
    SecretSettingDoc,
  ) as unknown as TSmartDataDocClass<SecretSettingDoc> & typeof SecretSettingDoc;
  public CUser = plugins.smartdata.setDefaultManagerForDoc(this, UserDoc) as unknown as
    & TSmartDataDocClass<UserDoc>
    & typeof UserDoc;
  public CMetric = plugins.smartdata.setDefaultManagerForDoc(this, MetricDoc) as unknown as
    & TSmartDataDocClass<MetricDoc>
    & typeof MetricDoc;
  public CLogEntry = plugins.smartdata.setDefaultManagerForDoc(this, LogEntryDoc) as unknown as
    & TSmartDataDocClass<LogEntryDoc>
    & typeof LogEntryDoc;
  public CDomain = plugins.smartdata.setDefaultManagerForDoc(this, DomainDoc) as unknown as
    & TSmartDataDocClass<DomainDoc>
    & typeof DomainDoc;
  public CCertificate = plugins.smartdata.setDefaultManagerForDoc(
    this,
    CertificateDoc,
  ) as unknown as TSmartDataDocClass<CertificateDoc> & typeof CertificateDoc;
  public CCertRequirement = plugins.smartdata.setDefaultManagerForDoc(
    this,
    CertRequirementDoc,
  ) as unknown as TSmartDataDocClass<CertRequirementDoc> & typeof CertRequirementDoc;
  public CDnsRecord = plugins.smartdata.setDefaultManagerForDoc(this, DnsRecordDoc) as unknown as
    & TSmartDataDocClass<DnsRecordDoc>
    & typeof DnsRecordDoc;
  public CPlatformService = plugins.smartdata.setDefaultManagerForDoc(
    this,
    PlatformServiceDoc,
  ) as unknown as TSmartDataDocClass<PlatformServiceDoc> & typeof PlatformServiceDoc;
  public CPlatformResource = plugins.smartdata.setDefaultManagerForDoc(
    this,
    PlatformResourceDoc,
  ) as unknown as TSmartDataDocClass<PlatformResourceDoc> & typeof PlatformResourceDoc;
  public CBackup = plugins.smartdata.setDefaultManagerForDoc(this, BackupDoc) as unknown as
    & TSmartDataDocClass<BackupDoc>
    & typeof BackupDoc;
  public CBackupSchedule = plugins.smartdata.setDefaultManagerForDoc(
    this,
    BackupScheduleDoc,
  ) as unknown as TSmartDataDocClass<BackupScheduleDoc> & typeof BackupScheduleDoc;

  public secretSettings!: SecretSettingsManager;
  public serviceSecrets!: ServiceSecretsManager;

  constructor(smartDbPath = './.nogit/smartdb') {
    this.smartDbPath = smartDbPath;
  }

  public get db(): plugins.smartdata.SmartdataDb {
    if (!this.smartdataDb) {
      throw new Error('Database not initialized');
    }
    return this.smartdataDb;
  }

  public async init(): Promise<void> {
    try {
      await plugins.fs.mkdir(this.smartDbPath, { recursive: true });

      this.localSmartDb = new plugins.smartdb.LocalSmartDb({
        folderPath: this.smartDbPath,
      });
      const connectionInfo = await this.localSmartDb.start();

      this.smartdataDb = new plugins.smartdata.SmartdataDb({
        mongoDbUrl: connectionInfo.connectionUri,
        mongoDbName: 'onebox',
      });
      await this.smartdataDb.init();

      this.serviceSecrets = new ServiceSecretsManager(this);
      await runStartupMigrations(this);
      await this.ensureDefaultStorageConfig();
      this.secretSettings = new SecretSettingsManager(this);

      logger.info(`SmartDB database initialized at ${this.smartDbPath}`);
    } catch (error) {
      logger.error(`Failed to initialize database: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  public async close(): Promise<void> {
    if (this.smartdataDb) {
      await this.smartdataDb.close();
      this.smartdataDb = null;
    }
    if (this.localSmartDb) {
      await this.localSmartDb.stop();
      this.localSmartDb = null;
    }
    logger.debug('SmartDB database connection closed');
  }

  private async getNextNumericId<TRecord extends TNumberedRecord>(
    DocClass: TSmartDataDocClass<TRecord>,
  ): Promise<number> {
    const docs = await DocClass.getInstances({});
    const ids = docs.map((docArg) => docArg.id || 0);
    return ids.length === 0 ? 1 : Math.max(...ids) + 1;
  }

  private createInitializedDoc<TDoc extends object, TRecord extends object>(
    DocClass: new () => TDoc,
    recordArg: TRecord,
  ): TDoc {
    const doc = new DocClass();
    Object.assign(doc, recordArg);
    return doc;
  }

  private async createDoc<TDoc extends object, TRecord extends object>(
    DocClass: new () => TDoc,
    recordArg: TRecord,
  ): Promise<TDoc> {
    const doc = this.createInitializedDoc(DocClass, recordArg);
    await smartDataRuntimeDoc(doc).save();
    return doc;
  }

  private async deleteNumberedDoc<TDoc extends TNumberedRecord>(
    DocClass: TSmartDataDocClass<TDoc>,
    idArg: number,
  ): Promise<void> {
    const doc = await DocClass.getInstance({ id: idArg });
    if (doc) {
      await smartDataRuntimeDoc(doc as object).delete();
    }
  }

  private async deleteKeyedDoc<TDoc extends TKeyedRecord>(
    DocClass: TSmartDataDocClass<TDoc>,
    keyArg: string,
  ): Promise<void> {
    const doc = await DocClass.getInstance({ key: keyArg });
    if (doc) {
      await smartDataRuntimeDoc(doc as object).delete();
    }
  }

  // ============ Services CRUD ============

  private normalizeServicePortFields(
    serviceArg: Pick<IService, 'name' | 'port' | 'domain' | 'targetPorts' | 'domains' | 'publicPortMappings'>,
  ): Pick<IService, 'targetPorts' | 'domains' | 'publicPortMappings'> {
    const targetPorts = serviceArg.targetPorts?.length
      ? serviceArg.targetPorts
      : [{ name: 'web', port: serviceArg.port, protocol: 'http' as const, default: true }];
    const normalizedPortData = plugins.servezoneInterfaces.data.normalizeServicePortData({
      name: serviceArg.name,
      targetPorts,
      domains: serviceArg.domains ?? (serviceArg.domain ? [{ name: serviceArg.domain, protocol: 'https' }] : []),
      publicPortMappings: serviceArg.publicPortMappings ?? [],
    }, { allowLegacyDomainPort: false });
    return {
      targetPorts: normalizedPortData.targetPorts,
      domains: normalizedPortData.domains ?? [],
      publicPortMappings: normalizedPortData.publicPortMappings ?? [],
    };
  }

  public async createService(service: Omit<IService, 'id'>): Promise<ServiceDoc> {
    const now = Date.now();
    const normalizedPortFields = this.normalizeServicePortFields({
      name: service.name,
      port: service.port,
      domain: service.domain,
      targetPorts: service.targetPorts,
      domains: service.domains,
      publicPortMappings: service.publicPortMappings,
    });
    const doc = await this.createDoc(ServiceDoc, {
      ...service,
      id: await this.getNextNumericId(this.CService),
      envVars: service.envVars || {},
      volumes: service.volumes || [],
      publishedPorts: service.publishedPorts || [],
      ...normalizedPortFields,
      status: service.status || 'stopped',
      createdAt: service.createdAt || now,
      updatedAt: now,
    });
    const fieldsToUnset = ['containerArgs', 'secretFiles'].filter(
      (fieldArg) => (service as unknown as Record<string, unknown>)[fieldArg] === undefined,
    );
    if (fieldsToUnset.length > 0) {
      await this.db.mongoDb.collection('ServiceDoc').updateOne(
        { id: doc.id },
        { $unset: Object.fromEntries(fieldsToUnset.map((fieldArg) => [fieldArg, ''])) },
        { upsert: false },
      );
    }
    return doc;
  }

  public async getServiceByName(name: string): Promise<ServiceDoc | null> {
    return await this.CService.getInstance({ name });
  }

  public async getServiceByID(id: number): Promise<ServiceDoc | null> {
    return await this.CService.getInstance({ id });
  }

  public async getAllServices(): Promise<ServiceDoc[]> {
    return (await this.CService.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async updateService(
    id: number,
    updates: Partial<Omit<IService, 'containerArgs' | 'secretFiles'>> & {
      containerArgs?: IService['containerArgs'] | null;
      secretFiles?: IService['secretFiles'] | null;
    },
  ): Promise<void> {
    const doc = await this.CService.getInstance({ id });
    if (!doc) return;
    const nextUpdates: Partial<IService> = { ...updates } as Partial<IService>;
    const fieldsToUnset: string[] = [];
    if (updates.containerArgs === null) {
      doc.containerArgs = undefined;
      delete nextUpdates.containerArgs;
      fieldsToUnset.push('containerArgs');
    }
    if (updates.secretFiles === null) {
      doc.secretFiles = undefined;
      delete nextUpdates.secretFiles;
      fieldsToUnset.push('secretFiles');
    }
    const touchesPortFields = updates.port !== undefined
      || updates.domain !== undefined
      || updates.targetPorts !== undefined
      || updates.domains !== undefined
      || updates.publicPortMappings !== undefined;
    if (touchesPortFields) {
      const previousPrimaryDomain = doc.domains?.find((domainArg) => domainArg.name === doc.domain)
        || doc.domains?.[0];
      const domains = updates.domains !== undefined
        ? updates.domains
        : updates.domain !== undefined
          ? updates.domain
            ? [{
                name: updates.domain,
                targetPort: previousPrimaryDomain?.targetPort,
                protocol: previousPrimaryDomain?.protocol || 'https',
                sourceProfileRef: previousPrimaryDomain?.sourceProfileRef,
              }]
            : []
          : doc.domains;
      Object.assign(
        nextUpdates,
        this.normalizeServicePortFields({
          name: updates.name || doc.name,
          port: updates.port ?? doc.port,
          domain: updates.domain !== undefined ? updates.domain : doc.domain,
          targetPorts: updates.targetPorts ?? doc.targetPorts,
          domains,
          publicPortMappings: updates.publicPortMappings ?? doc.publicPortMappings,
        }),
      );
    }
    applyDefinedUpdates(
      doc as unknown as Record<string, unknown>,
      nextUpdates as Record<string, unknown>,
    );
    doc.updatedAt = Date.now();
    await doc.save();
    if (fieldsToUnset.length > 0) {
      await this.db.mongoDb.collection('ServiceDoc').updateOne(
        { id },
        { $unset: Object.fromEntries(fieldsToUnset.map((fieldArg) => [fieldArg, ''])) },
        { upsert: false },
      );
    }
  }

  public async replaceService(idArg: number, serviceArg: IService): Promise<void> {
    if (serviceArg.id !== idArg) {
      throw new Error(`Service rollback id mismatch: expected ${idArg}, received ${serviceArg.id}`);
    }
    const doc = await this.CService.getInstance({ id: idArg });
    if (!doc) {
      throw new Error(`Cannot restore missing service ${idArg}`);
    }
    Object.assign(doc, serviceArg);
    await doc.save();
    const optionalFields: Array<keyof IService> = [
      'registry',
      'containerArgs',
      'secretFiles',
      'volumes',
      'publishedPorts',
      'targetPorts',
      'domains',
      'publicPortMappings',
      'domain',
      'containerID',
      'useOneboxRegistry',
      'registryRepository',
      'registryImageTag',
      'autoUpdateOnPush',
      'imageDigest',
      'imageCleanup',
      'platformRequirements',
      'includeImageInBackup',
      'appTemplateId',
      'appTemplateVersion',
    ];
    const fieldsToUnset = optionalFields.filter((fieldArg) => serviceArg[fieldArg] == null);
    if (fieldsToUnset.length > 0) {
      await this.db.mongoDb.collection('ServiceDoc').updateOne(
        { id: idArg },
        { $unset: Object.fromEntries(fieldsToUnset.map((fieldArg) => [fieldArg, ''])) },
        { upsert: false },
      );
    }
  }

  public async deleteService(id: number): Promise<void> {
    for (const lifecycle of await this.CHostedAppLifecycle.getInstances({ serviceId: id })) {
      await lifecycle.delete();
    }
    await this.deleteCertRequirementsByService(id);
    await this.deleteMetricsByService(id);
    await this.deleteLogsByService(id);
    await this.deleteNumberedDoc(this.CService, id);
    await this.serviceSecrets.clear(id);
  }

  public async markServiceRuntimeCleanupRequired(
    idArg: number,
    containerIDArg?: string,
  ): Promise<void> {
    if (!Number.isSafeInteger(idArg) || idArg < 1) {
      throw new Error(`Invalid service id: ${idArg}`);
    }
    const updateFields: Record<string, unknown> = {
      status: 'failed',
      updatedAt: Date.now(),
    };
    if (containerIDArg) updateFields.containerID = containerIDArg;
    const result = await this.db.mongoDb.collection('ServiceDoc').updateOne(
      { id: idArg },
      containerIDArg
        ? { $set: updateFields }
        : { $set: updateFields, $unset: { containerID: '' } },
      { upsert: false },
    );
    if (result.matchedCount !== 1) {
      throw new Error(`Service ${idArg} not found while preserving failed runtime ownership`);
    }
  }

  public async clearServiceImageCleanup(idArg: number): Promise<void> {
    if (!Number.isSafeInteger(idArg) || idArg < 1) {
      throw new Error(`Invalid service id: ${idArg}`);
    }
    const result = await this.db.mongoDb.collection('ServiceDoc').updateOne(
      { id: idArg },
      { $unset: { imageCleanup: '' } },
      { upsert: false },
    );
    if (result.matchedCount !== 1) {
      throw new Error(`Service ${idArg} not found while clearing image cleanup ownership`);
    }
  }

  // ============ Hosted App Lifecycle ============

  public async upsertHostedAppLifecycle(
    recordArg: Omit<IHostedAppLifecycleRecord, 'id' | 'createdAt' | 'updatedAt'>,
  ): Promise<HostedAppLifecycleDoc> {
    const now = Date.now();
    const existing = await this.getHostedAppLifecycleByServiceId(recordArg.serviceId);
    if (existing) {
      existing.appInstanceId = recordArg.appInstanceId;
      existing.hostType = 'onebox';
      existing.state = recordArg.state;
      existing.updatedAt = now;
      await existing.save();
      return existing;
    }

    return await this.createDoc(HostedAppLifecycleDoc, {
      ...recordArg,
      id: await this.getNextNumericId(this.CHostedAppLifecycle),
      hostType: 'onebox' as const,
      createdAt: now,
      updatedAt: now,
    });
  }

  public async getRawServiceSecrets(serviceIdArg: number): Promise<string | null> {
    if (!Number.isSafeInteger(serviceIdArg) || serviceIdArg < 1) {
      throw new Error(`Invalid service secret owner id: ${serviceIdArg}`);
    }
    const bundle = await this.CServiceSecretBundle.getInstance({ serviceId: serviceIdArg });
    return bundle?.valuesEncrypted ?? null;
  }

  public async setRawServiceSecrets(serviceIdArg: number, encryptedValuesArg: string): Promise<void> {
    if (!Number.isSafeInteger(serviceIdArg) || serviceIdArg < 1) {
      throw new Error(`Invalid service secret owner id: ${serviceIdArg}`);
    }
    if (typeof encryptedValuesArg !== 'string' || encryptedValuesArg.length === 0) {
      throw new Error('Encrypted service secret values are required');
    }
    const now = Date.now();
    let bundle = await this.CServiceSecretBundle.getInstance({ serviceId: serviceIdArg });
    if (!bundle) {
      bundle = this.createInitializedDoc(ServiceSecretBundleDoc, {
        serviceId: serviceIdArg,
        valuesEncrypted: encryptedValuesArg,
        createdAt: now,
        updatedAt: now,
      });
    } else {
      bundle.valuesEncrypted = encryptedValuesArg;
      bundle.updatedAt = now;
    }
    await bundle.save();
  }

  public async deleteRawServiceSecrets(serviceIdArg: number): Promise<void> {
    if (!Number.isSafeInteger(serviceIdArg) || serviceIdArg < 1) {
      throw new Error(`Invalid service secret owner id: ${serviceIdArg}`);
    }
    const bundle = await this.CServiceSecretBundle.getInstance({ serviceId: serviceIdArg });
    if (bundle) await bundle.delete();
  }

  public async getHostedAppLifecycleByServiceId(
    serviceIdArg: number,
  ): Promise<HostedAppLifecycleDoc | null> {
    return await this.CHostedAppLifecycle.getInstance({ serviceId: serviceIdArg });
  }

  public async getHostedAppLifecycleByInstanceId(
    appInstanceIdArg: string,
  ): Promise<HostedAppLifecycleDoc | null> {
    return await this.CHostedAppLifecycle.getInstance({ appInstanceId: appInstanceIdArg });
  }

  public async updateHostedAppLifecycleState(
    serviceIdArg: number,
    stateArg: IHostedAppLifecycleState,
  ): Promise<HostedAppLifecycleDoc> {
    const doc = await this.getHostedAppLifecycleByServiceId(serviceIdArg);
    if (!doc) throw new Error(`Hosted app lifecycle not found for service ${serviceIdArg}`);
    doc.state = stateArg;
    doc.updatedAt = Date.now();
    await doc.save();
    return doc;
  }

  // ============ Registries CRUD ============

  public async createRegistry(registry: Omit<IRegistry, 'id'>): Promise<RegistryDoc> {
    const now = Date.now();
    return await this.createDoc(RegistryDoc, {
      ...registry,
      id: await this.getNextNumericId(this.CRegistry),
      createdAt: registry.createdAt || now,
    });
  }

  public async getRegistryByURL(url: string): Promise<RegistryDoc | null> {
    return await this.CRegistry.getInstance({ url });
  }

  public async getAllRegistries(): Promise<RegistryDoc[]> {
    return (await this.CRegistry.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async deleteRegistry(url: string): Promise<void> {
    const registry = await this.getRegistryByURL(url);
    if (!registry) return;
    await registry.delete();
  }

  // ============ Storage Targets And Classes ============

  private normalizeStorageName(nameArg: string): string {
    const name = nameArg.trim().toLowerCase();
    if (!/^[a-z0-9][a-z0-9_.-]{1,80}$/.test(name)) {
      throw new Error(`Invalid storage name: ${nameArg}`);
    }
    return name;
  }

  private getDefaultBackupPath(): string {
    return process.env.ONEBOX_DEV === 'true' ? './.nogit/backups' : '/var/lib/onebox/backups';
  }

  private getDefaultPlatformDataBaseDir(): string {
    return process.env.ONEBOX_DEV === 'true' ? './.nogit/platform-data' : '/var/lib/onebox';
  }

  private getStorageVolumeDirectoryName(volumeArg: IServiceVolume): string {
    const candidate = volumeArg.source || volumeArg.name ||
      volumeArg.mountPath.replace(/^\/+/, '').replace(/\/+$/g, '');
    return candidate
      .replace(/[^a-zA-Z0-9_.-]+/g, '-')
      .replace(/^[^a-zA-Z0-9]+|[^a-zA-Z0-9]+$/g, '') || 'data';
  }

  private async upsertDefaultStorageTarget(
    targetArg: Omit<IStorageTarget, 'id' | 'createdAt' | 'updatedAt'>,
  ): Promise<void> {
    const existing = await this.getStorageTargetByName(targetArg.name);
    if (existing) return;
    await this.createStorageTarget(targetArg);
  }

  private async upsertDefaultStorageClass(
    storageClassArg: Omit<IStorageClass, 'id' | 'createdAt' | 'updatedAt'>,
  ): Promise<void> {
    const existing = await this.getStorageClassByName(storageClassArg.name);
    if (existing) return;
    await this.createStorageClass(storageClassArg);
  }

  public async ensureDefaultStorageConfig(): Promise<void> {
    await this.upsertDefaultStorageTarget({
      name: 'local',
      displayName: 'Local Docker Volumes',
      kind: 'local',
      config: {},
    });
    await this.upsertDefaultStorageTarget({
      name: 'local-hot',
      displayName: 'Local Hot Storage',
      kind: 'local',
      config: { path: this.getDefaultPlatformDataBaseDir() },
    });
    await this.upsertDefaultStorageTarget({
      name: 'local-backup',
      displayName: 'Local Backup Storage',
      kind: 'backup',
      config: { path: this.getDefaultBackupPath() },
    });
    await this.upsertDefaultStorageClass({
      name: 'local-runtime',
      displayName: 'Local Runtime',
      description: 'Default local Docker volume storage for application volumes.',
      targetName: 'local',
      purpose: 'runtime',
      isDefault: true,
    });
    await this.upsertDefaultStorageClass({
      name: 'local-hot',
      displayName: 'Local Hot',
      description: 'Default low-latency local storage for platform databases.',
      targetName: 'local-hot',
      purpose: 'database',
      isDefault: true,
    });
    await this.upsertDefaultStorageClass({
      name: 'local-backup',
      displayName: 'Local Backup',
      description: 'Default local storage target for backups.',
      targetName: 'local-backup',
      purpose: 'backup',
      isDefault: true,
    });
  }

  public async createStorageTarget(
    targetArg: Omit<IStorageTarget, 'id' | 'createdAt' | 'updatedAt'>,
  ): Promise<StorageTargetDoc> {
    const now = Date.now();
    const name = this.normalizeStorageName(targetArg.name);
    return await this.createDoc(StorageTargetDoc, {
      ...targetArg,
      name,
      id: await this.getNextNumericId(this.CStorageTarget),
      config: targetArg.config || {},
      createdAt: now,
      updatedAt: now,
    });
  }

  public async upsertStorageTarget(
    targetArg: Omit<IStorageTarget, 'id' | 'createdAt' | 'updatedAt'> & { id?: number },
  ): Promise<StorageTargetDoc> {
    const now = Date.now();
    const name = this.normalizeStorageName(targetArg.name);
    const existing = await this.CStorageTarget.getInstance({ name });
    if (existing) {
      existing.displayName = targetArg.displayName;
      existing.kind = targetArg.kind;
      existing.config = targetArg.config || {};
      existing.updatedAt = now;
      await existing.save();
      return existing;
    }
    return await this.createStorageTarget({ ...targetArg, name });
  }

  public async getStorageTargetByName(nameArg: string): Promise<StorageTargetDoc | null> {
    return await this.CStorageTarget.getInstance({ name: this.normalizeStorageName(nameArg) });
  }

  public async getAllStorageTargets(): Promise<StorageTargetDoc[]> {
    return (await this.CStorageTarget.getInstances({})).sort((a, b) =>
      a.name.localeCompare(b.name)
    );
  }

  public async deleteStorageTarget(nameArg: string): Promise<void> {
    const target = await this.getStorageTargetByName(nameArg);
    if (!target) return;
    const classes = await this.CStorageClass.getInstances({ targetName: target.name });
    if (classes.length > 0) {
      throw new Error(
        `Storage target ${target.name} is still used by ${classes.length} storage class(es)`,
      );
    }
    await target.delete();
  }

  public async createStorageClass(
    storageClassArg: Omit<IStorageClass, 'id' | 'createdAt' | 'updatedAt'>,
  ): Promise<StorageClassDoc> {
    const now = Date.now();
    const name = this.normalizeStorageName(storageClassArg.name);
    const targetName = this.normalizeStorageName(storageClassArg.targetName);
    const target = await this.getStorageTargetByName(targetName);
    if (!target) {
      throw new Error(`Storage target not found: ${targetName}`);
    }
    if (storageClassArg.isDefault) {
      await this.clearDefaultStorageClass(storageClassArg.purpose);
    }
    return await this.createDoc(StorageClassDoc, {
      ...storageClassArg,
      name,
      targetName,
      id: await this.getNextNumericId(this.CStorageClass),
      options: storageClassArg.options || {},
      createdAt: now,
      updatedAt: now,
    });
  }

  public async upsertStorageClass(
    storageClassArg: Omit<IStorageClass, 'id' | 'createdAt' | 'updatedAt'> & { id?: number },
  ): Promise<StorageClassDoc> {
    const now = Date.now();
    const name = this.normalizeStorageName(storageClassArg.name);
    const targetName = this.normalizeStorageName(storageClassArg.targetName);
    const target = await this.getStorageTargetByName(targetName);
    if (!target) {
      throw new Error(`Storage target not found: ${targetName}`);
    }
    const existing = await this.CStorageClass.getInstance({ name });
    if (storageClassArg.isDefault) {
      await this.clearDefaultStorageClass(storageClassArg.purpose, existing?.id);
    }
    if (existing) {
      existing.displayName = storageClassArg.displayName;
      existing.description = storageClassArg.description;
      existing.targetName = targetName;
      existing.purpose = storageClassArg.purpose;
      existing.isDefault = storageClassArg.isDefault;
      existing.driver = storageClassArg.driver;
      existing.options = storageClassArg.options || {};
      existing.updatedAt = now;
      await existing.save();
      return existing;
    }
    return await this.createStorageClass({ ...storageClassArg, name, targetName });
  }

  public async clearDefaultStorageClass(
    purposeArg: TStorageClassPurpose,
    exceptIdArg?: number,
  ): Promise<void> {
    const classes = await this.CStorageClass.getInstances({ purpose: purposeArg, isDefault: true });
    for (const storageClass of classes) {
      if (exceptIdArg && storageClass.id === exceptIdArg) continue;
      storageClass.isDefault = false;
      storageClass.updatedAt = Date.now();
      await storageClass.save();
    }
  }

  public async getStorageClassByName(nameArg: string): Promise<StorageClassDoc | null> {
    return await this.CStorageClass.getInstance({ name: this.normalizeStorageName(nameArg) });
  }

  public async getAllStorageClasses(): Promise<StorageClassDoc[]> {
    return (await this.CStorageClass.getInstances({})).sort((a, b) => a.name.localeCompare(b.name));
  }

  public async getDefaultStorageClassName(purposeArg: TStorageClassPurpose): Promise<string> {
    const defaultClass =
      (await this.CStorageClass.getInstances({ purpose: purposeArg, isDefault: true }))[0];
    if (defaultClass) return defaultClass.name;
    if (purposeArg === 'database') return 'local-hot';
    if (purposeArg === 'backup') return 'local-backup';
    return 'local-runtime';
  }

  public async deleteStorageClass(nameArg: string): Promise<void> {
    const storageClass = await this.getStorageClassByName(nameArg);
    if (!storageClass) return;
    if (storageClass.isDefault) {
      throw new Error(`Cannot delete default storage class: ${storageClass.name}`);
    }
    await storageClass.delete();
  }

  public async resolveServiceVolumeStorage(
    volumeArg: IServiceVolume,
    purposeArg: TStorageClassPurpose = 'runtime',
  ): Promise<IServiceVolume> {
    const storageClassName = volumeArg.storageClass ||
      await this.getDefaultStorageClassName(purposeArg);
    const storageClass = await this.getStorageClassByName(storageClassName);
    if (!storageClass) {
      throw new Error(`Storage class not found: ${storageClassName}`);
    }
    const target = await this.getStorageTargetByName(storageClass.targetName);
    if (!target) {
      throw new Error(`Storage target not found: ${storageClass.targetName}`);
    }

    const resolved = await this.getDockerVolumeDriverConfig(target, storageClass, volumeArg);
    return {
      ...volumeArg,
      storageClass: storageClass.name,
      driver: volumeArg.driver || storageClass.driver || resolved.driver,
      options: {
        ...resolved.options,
        ...(storageClass.options || {}),
        ...(volumeArg.options || {}),
      },
    };
  }

  private async getDockerVolumeDriverConfig(
    targetArg: IStorageTarget,
    storageClassArg: IStorageClass,
    volumeArg: IServiceVolume,
  ): Promise<{ driver: string; options: Record<string, string> }> {
    const config = targetArg.config || {};
    const baseOptions = config.options || {};
    if (targetArg.kind === 'local' || targetArg.kind === 'backup') {
      if (config.path) {
        const volumePath = plugins.path.join(
          config.path,
          this.getStorageVolumeDirectoryName(volumeArg),
        );
        await plugins.fs.mkdir(volumePath, { recursive: true });
        return {
          driver: storageClassArg.driver || 'local',
          options: { type: 'none', o: 'bind', device: volumePath, ...baseOptions },
        };
      }
      return { driver: storageClassArg.driver || 'local', options: { ...baseOptions } };
    }

    if (targetArg.kind === 'nfs') {
      if (!config.server || !config.exportPath) {
        throw new Error(`NFS storage target ${targetArg.name} requires server and exportPath`);
      }
      const mountOptions = [
        `addr=${config.server}`,
        'rw',
        config.version ? `nfsvers=${config.version}` : undefined,
        ...Object.entries(baseOptions).map(([keyArg, valueArg]) => `${keyArg}=${valueArg}`),
      ].filter(Boolean).join(',');
      return {
        driver: storageClassArg.driver || 'local',
        options: { type: 'nfs', o: mountOptions, device: `:${config.exportPath}` },
      };
    }

    if (targetArg.kind === 'smb') {
      if (!config.server || !config.share) {
        throw new Error(`SMB storage target ${targetArg.name} requires server and share`);
      }
      const mountOptions = [
        'rw',
        config.username ? `username=${config.username}` : undefined,
        config.password ? `password=${config.password}` : undefined,
        config.domain ? `domain=${config.domain}` : undefined,
        config.version ? `vers=${config.version}` : undefined,
        ...Object.entries(baseOptions).map(([keyArg, valueArg]) => `${keyArg}=${valueArg}`),
      ].filter(Boolean).join(',');
      return {
        driver: storageClassArg.driver || 'local',
        options: { type: 'cifs', o: mountOptions, device: `//${config.server}/${config.share}` },
      };
    }

    return {
      driver: storageClassArg.driver || config.driver || 'local',
      options: {
        ...baseOptions,
        ...(config.device ? { device: config.device } : {}),
      },
    };
  }

  // ============ Settings CRUD ============

  public async getSetting(key: string): Promise<string | null> {
    const setting = await this.CSetting.getInstance({ key });
    return setting?.value ?? null;
  }

  public async setSetting(key: string, value: string): Promise<void> {
    const now = Date.now();
    let doc = await this.CSetting.getInstance({ key });
    if (!doc) {
      doc = this.createInitializedDoc(SettingDoc, { key, value, updatedAt: now });
    } else {
      doc.value = value;
      doc.updatedAt = now;
    }
    await doc.save();
  }

  public async deleteSetting(key: string): Promise<void> {
    await this.deleteKeyedDoc(this.CSetting, key);
  }

  public async getAllSettings(): Promise<Record<string, string>> {
    const settings: Record<string, string> = {};
    for (const doc of await this.CSetting.getInstances({})) {
      settings[doc.key] = doc.value;
    }
    return settings;
  }

  public async getRawSecretSetting(key: string): Promise<string | null> {
    const setting = await this.CSecretSetting.getInstance({ key });
    return setting?.value ?? null;
  }

  public async setRawSecretSetting(key: string, value: string): Promise<void> {
    const now = Date.now();
    let doc = await this.CSecretSetting.getInstance({ key });
    if (!doc) {
      doc = this.createInitializedDoc(SecretSettingDoc, { key, value, updatedAt: now });
    } else {
      doc.value = value;
      doc.updatedAt = now;
    }
    await doc.save();
  }

  public async deleteRawSecretSetting(key: string): Promise<void> {
    await this.deleteKeyedDoc(this.CSecretSetting, key);
  }

  public async getSecretSetting(key: string): Promise<string | null> {
    return await this.secretSettings.get(key);
  }

  public async setSecretSetting(key: string, value: string | null): Promise<void> {
    await this.secretSettings.set(key, value);
  }

  public async deleteSecretSetting(key: string): Promise<void> {
    await this.secretSettings.clear(key);
  }

  public async hasSecretSetting(key: string): Promise<boolean> {
    return await this.secretSettings.has(key);
  }

  public isSecretSettingKey(key: string): boolean {
    return this.secretSettings.isSecretKey(key);
  }

  public getCanonicalSecretSettingKeys(): string[] {
    return this.secretSettings.getCanonicalKeys();
  }

  // ============ Users CRUD ============

  public async createUser(user: Omit<IUser, 'id'>): Promise<UserDoc> {
    const now = Date.now();
    const username = requireSelectorString(user.username, 'username');
    const webauthnUserId = requireBase64urlString(user.webauthnUserId, 'webauthnUserId');
    if (
      user.role !== 'admin'
      && user.role !== 'user'
    ) {
      throw new Error('role is missing or malformed');
    }
    if (!Array.isArray(user.passkeyCredentials)) {
      throw new Error('passkeyCredentials is missing or malformed');
    }
    const passkeyCredentials = user.passkeyCredentials.map(normalizePasskeyCredential);
    const createdAt = requireNonNegativeInteger(user.createdAt || now, 'createdAt');
    const updatedAt = requireNonNegativeInteger(user.updatedAt || now, 'updatedAt');
    return await this.createDoc(UserDoc, {
      ...user,
      username,
      webauthnUserId,
      passkeyCredentials,
      id: await this.getNextNumericId(this.CUser),
      createdAt,
      updatedAt,
    });
  }

  public async getUserByUsername(username: string): Promise<UserDoc | null> {
    const validatedUsername = requireSelectorString(username, 'username');
    return await this.CUser.getInstance({ username: validatedUsername });
  }

  public async ensurePasskeyAdminUser(
    username: string,
    webauthnUserId: string,
  ): Promise<UserDoc> {
    const validatedUsername = requireSelectorString(username, 'username');
    const validatedWebauthnUserId = requireBase64urlString(webauthnUserId, 'webauthnUserId');
    let user = await this.getUserByUsername(validatedUsername);
    if (!user) {
      return await this.createUser({
        username: validatedUsername,
        role: 'admin',
        webauthnUserId: validatedWebauthnUserId,
        passkeyCredentials: [],
        createdAt: Date.now(),
        updatedAt: Date.now(),
      });
    }

    if (user.role !== 'admin') {
      throw new Error('Configured Onebox administrator does not have the admin role');
    }

    const rawUser = user as unknown as Partial<IUser>;
    if (
      rawUser.passkeyCredentials !== undefined
      && rawUser.passkeyCredentials !== null
      && !Array.isArray(rawUser.passkeyCredentials)
    ) {
      throw new Error('Onebox administrator passkey data is malformed');
    }

    const updates: Record<string, unknown> = {};
    if (!rawUser.webauthnUserId) {
      updates.webauthnUserId = validatedWebauthnUserId;
    }
    if (!Array.isArray(rawUser.passkeyCredentials)) {
      updates.passkeyCredentials = [];
    }
    if (Object.keys(updates).length > 0) {
      updates.updatedAt = Date.now();
      const userId = requireNonNegativeInteger(user.id, 'user id');
      await this.db.mongoDb.collection('UserDoc').updateOne(
        { id: userId, username: validatedUsername, role: 'admin' },
        { $set: updates },
        { upsert: false },
      );
      user = await this.getUserByUsername(validatedUsername);
    }

    if (!user?.webauthnUserId || !Array.isArray(user.passkeyCredentials)) {
      throw new Error('Failed to initialize Onebox administrator passkey identity');
    }
    requireBase64urlString(user.webauthnUserId, 'persisted webauthnUserId');
    for (const credential of user.passkeyCredentials) {
      normalizePasskeyCredential(credential);
    }
    return user;
  }

  public async getAdminPasskeyCredentialById(
    username: string,
    credentialId: string,
  ): Promise<{ user: UserDoc; credential: IPasskeyCredential } | null> {
    const validatedUsername = requireSelectorString(username, 'username');
    const validatedCredentialId = requireBase64urlString(credentialId, 'credentialId');
    const user = await this.CUser.getInstance({
      username: validatedUsername,
      role: 'admin',
      'passkeyCredentials.credentialId': validatedCredentialId,
    });
    const credential = user?.passkeyCredentials.find(
      (credentialArg) => credentialArg.credentialId === validatedCredentialId,
    );
    if (!user || !credential) {
      return null;
    }
    return { user, credential: normalizePasskeyCredential(credential) };
  }

  public async registerInitialPasskey(
    username: string,
    webauthnUserId: string,
    credential: IPasskeyCredential,
  ): Promise<boolean> {
    const validatedUsername = requireSelectorString(username, 'username');
    const validatedWebauthnUserId = requireBase64urlString(webauthnUserId, 'webauthnUserId');
    const normalizedCredential = normalizePasskeyCredential(credential);
    const result = await this.db.mongoDb.collection('UserDoc').updateOne(
      {
        username: validatedUsername,
        role: 'admin',
        webauthnUserId: validatedWebauthnUserId,
        $or: [
          { passkeyCredentials: { $exists: false } },
          { passkeyCredentials: null },
          { passkeyCredentials: { $size: 0 } },
        ],
      },
      {
        $set: {
          passkeyCredentials: [normalizedCredential],
          updatedAt: Date.now(),
        },
        $unset: { passwordHash: '' },
      },
      { upsert: false },
    );
    return result.matchedCount === 1;
  }

  public async updatePasskeyCounter(
    username: string,
    credentialId: string,
    expectedCounter: number,
    newCounter: number,
  ): Promise<boolean> {
    const validatedUsername = requireSelectorString(username, 'username');
    const validatedCredentialId = requireBase64urlString(credentialId, 'credentialId');
    const validatedExpectedCounter = requireNonNegativeInteger(expectedCounter, 'expectedCounter');
    const validatedNewCounter = requireNonNegativeInteger(newCounter, 'newCounter');
    const result = await this.db.mongoDb.collection('UserDoc').updateOne(
      {
        username: validatedUsername,
        role: 'admin',
        passkeyCredentials: {
          $elemMatch: { credentialId: validatedCredentialId, counter: validatedExpectedCounter },
        },
      },
      {
        $set: {
          'passkeyCredentials.$.counter': validatedNewCounter,
          'passkeyCredentials.$.lastUsedAt': Date.now(),
          updatedAt: Date.now(),
        },
      },
      { upsert: false },
    );
    return result.matchedCount === 1;
  }

  public async resetAdminPasskeys(
    username: string,
    newWebauthnUserId: string,
  ): Promise<boolean> {
    const validatedUsername = requireSelectorString(username, 'username');
    const validatedWebauthnUserId = requireBase64urlString(newWebauthnUserId, 'webauthnUserId');
    const result = await this.db.mongoDb.collection('UserDoc').updateOne(
      {
        username: validatedUsername,
        role: 'admin',
      },
      {
        $set: {
          webauthnUserId: validatedWebauthnUserId,
          passkeyCredentials: [],
          updatedAt: Date.now(),
        },
        $unset: {
          passwordHash: '',
        },
      },
      { upsert: false },
    );
    return result.matchedCount === 1;
  }

  public async getAllUsers(): Promise<UserDoc[]> {
    return (await this.CUser.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async deleteUser(username: string): Promise<void> {
    const user = await this.getUserByUsername(username);
    if (!user) return;
    await user.delete();
  }

  // ============ Metrics ============

  public async addMetric(metric: Omit<IMetric, 'id'>): Promise<void> {
    await this.createDoc(MetricDoc, {
      ...metric,
      id: await this.getNextNumericId(this.CMetric),
    });
  }

  public async getMetrics(serviceId: number, limit = 100): Promise<MetricDoc[]> {
    return (await this.CMetric.getInstances({ serviceId }))
      .sort((a, b) => b.timestamp - a.timestamp)
      .slice(0, limit);
  }

  public async deleteMetricsByService(serviceId: number): Promise<void> {
    for (const metric of await this.CMetric.getInstances({ serviceId })) {
      await metric.delete();
    }
  }

  // ============ Logs ============

  public async addLog(log: Omit<ILogEntry, 'id'>): Promise<void> {
    await this.createDoc(LogEntryDoc, {
      ...log,
      id: await this.getNextNumericId(this.CLogEntry),
    });
  }

  public async getLogs(serviceId: number, limit = 1000): Promise<LogEntryDoc[]> {
    return (await this.CLogEntry.getInstances({ serviceId }))
      .sort((a, b) => b.timestamp - a.timestamp)
      .slice(0, limit);
  }

  public async deleteLogsByService(serviceId: number): Promise<void> {
    for (const log of await this.CLogEntry.getInstances({ serviceId })) {
      await log.delete();
    }
  }

  // ============ SSL Certificates Legacy API ============

  public async createSSLCertificate(cert: Omit<ISslCertificate, 'id'>): Promise<ISslCertificate> {
    let domainRecord = await this.getDomainByName(cert.domain);
    if (!domainRecord) {
      const now = Date.now();
      domainRecord = await this.createDomain({
        domain: cert.domain,
        dnsProvider: null,
        isObsolete: false,
        defaultWildcard: true,
        createdAt: now,
        updatedAt: now,
      });
    }

    const existing = (await this.getAllCertificates()).find((certArg) =>
      certArg.certDomain === cert.domain
    );
    if (existing?.id) {
      await this.updateCertificate(existing.id, {
        certPem: cert.certPem,
        keyPem: cert.keyPem,
        fullchainPem: cert.fullchainPem,
        expiryDate: cert.expiryDate,
        issuer: cert.issuer,
        isValid: true,
      });
      return (await this.getSSLCertificate(cert.domain))!;
    }

    const now = Date.now();
    await this.createCertificate({
      domainId: domainRecord.id!,
      certDomain: cert.domain,
      isWildcard: false,
      certPem: cert.certPem,
      keyPem: cert.keyPem,
      fullchainPem: cert.fullchainPem,
      expiryDate: cert.expiryDate,
      issuer: cert.issuer,
      isValid: true,
      createdAt: cert.createdAt || now,
      updatedAt: cert.updatedAt || now,
    });
    return (await this.getSSLCertificate(cert.domain))!;
  }

  public async getSSLCertificate(domain: string): Promise<ISslCertificate | null> {
    const cert = (await this.getAllCertificates()).find((certArg) => certArg.certDomain === domain);
    if (!cert) return null;
    return {
      id: cert.id,
      domain: cert.certDomain,
      certPem: cert.certPem,
      keyPem: cert.keyPem,
      fullchainPem: cert.fullchainPem,
      expiryDate: cert.expiryDate,
      issuer: cert.issuer,
      createdAt: cert.createdAt,
      updatedAt: cert.updatedAt,
    };
  }

  public async getAllSSLCertificates(): Promise<ISslCertificate[]> {
    return (await this.getAllCertificates())
      .map((certArg) => ({
        id: certArg.id,
        domain: certArg.certDomain,
        certPem: certArg.certPem,
        keyPem: certArg.keyPem,
        fullchainPem: certArg.fullchainPem,
        expiryDate: certArg.expiryDate,
        issuer: certArg.issuer,
        createdAt: certArg.createdAt,
        updatedAt: certArg.updatedAt,
      }))
      .sort((a, b) => a.expiryDate - b.expiryDate);
  }

  public async updateSSLCertificate(
    domain: string,
    updates: Partial<ISslCertificate>,
  ): Promise<void> {
    const cert = (await this.getAllCertificates()).find((certArg) => certArg.certDomain === domain);
    if (!cert?.id) return;
    await this.updateCertificate(cert.id, {
      certPem: updates.certPem,
      keyPem: updates.keyPem,
      fullchainPem: updates.fullchainPem,
      expiryDate: updates.expiryDate,
      issuer: updates.issuer,
    });
  }

  public async deleteSSLCertificate(domain: string): Promise<void> {
    const cert = (await this.getAllCertificates()).find((certArg) => certArg.certDomain === domain);
    if (!cert?.id) return;
    await this.deleteCertificate(cert.id);
  }

  // ============ Domains ============

  public async createDomain(domain: Omit<IDomain, 'id'>): Promise<DomainDoc> {
    return await this.createDoc(DomainDoc, {
      ...domain,
      id: await this.getNextNumericId(this.CDomain),
    });
  }

  public async getDomainByName(domain: string): Promise<DomainDoc | null> {
    return await this.CDomain.getInstance({ domain });
  }

  public async getDomainById(id: number): Promise<DomainDoc | null> {
    return await this.CDomain.getInstance({ id });
  }

  public async getAllDomains(): Promise<DomainDoc[]> {
    return (await this.CDomain.getInstances({})).sort((a, b) => a.domain.localeCompare(b.domain));
  }

  public async getDomainsByProvider(
    provider: NonNullable<IDomain['dnsProvider']>,
  ): Promise<DomainDoc[]> {
    return (await this.CDomain.getInstances({ dnsProvider: provider })).sort((a, b) =>
      a.domain.localeCompare(b.domain)
    );
  }

  public async updateDomain(id: number, updates: Partial<IDomain>): Promise<void> {
    const doc = await this.CDomain.getInstance({ id });
    if (!doc) return;
    applyDefinedUpdates(
      doc as unknown as Record<string, unknown>,
      updates as Record<string, unknown>,
    );
    doc.updatedAt = Date.now();
    await doc.save();
  }

  public async deleteDomain(id: number): Promise<void> {
    await this.deleteNumberedDoc(this.CDomain, id);
  }

  // ============ Certificates ============

  public async createCertificate(cert: Omit<ICertificate, 'id'>): Promise<CertificateDoc> {
    return await this.createDoc(CertificateDoc, {
      ...cert,
      id: await this.getNextNumericId(this.CCertificate),
    });
  }

  public async getCertificateById(id: number): Promise<CertificateDoc | null> {
    return await this.CCertificate.getInstance({ id });
  }

  public async getCertificatesByDomain(domainId: number): Promise<CertificateDoc[]> {
    return (await this.CCertificate.getInstances({ domainId })).sort((a, b) =>
      b.expiryDate - a.expiryDate
    );
  }

  public async getAllCertificates(): Promise<CertificateDoc[]> {
    return (await this.CCertificate.getInstances({})).sort((a, b) => b.expiryDate - a.expiryDate);
  }

  public async updateCertificate(id: number, updates: Partial<ICertificate>): Promise<void> {
    const doc = await this.CCertificate.getInstance({ id });
    if (!doc) return;
    applyDefinedUpdates(
      doc as unknown as Record<string, unknown>,
      updates as Record<string, unknown>,
    );
    doc.updatedAt = Date.now();
    await doc.save();
  }

  public async deleteCertificate(id: number): Promise<void> {
    await this.deleteNumberedDoc(this.CCertificate, id);
  }

  // ============ Certificate Requirements ============

  public async createCertRequirement(
    req: Omit<ICertRequirement, 'id'>,
  ): Promise<CertRequirementDoc> {
    return await this.createDoc(CertRequirementDoc, {
      ...req,
      id: await this.getNextNumericId(this.CCertRequirement),
    });
  }

  public async getCertRequirementById(id: number): Promise<CertRequirementDoc | null> {
    return await this.CCertRequirement.getInstance({ id });
  }

  public async getCertRequirementsByService(serviceId: number): Promise<CertRequirementDoc[]> {
    return await this.CCertRequirement.getInstances({ serviceId });
  }

  public async getCertRequirementsByDomain(domainId: number): Promise<CertRequirementDoc[]> {
    return await this.CCertRequirement.getInstances({ domainId });
  }

  public async getAllCertRequirements(): Promise<CertRequirementDoc[]> {
    return (await this.CCertRequirement.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async updateCertRequirement(
    id: number,
    updates: Partial<ICertRequirement>,
  ): Promise<void> {
    const doc = await this.CCertRequirement.getInstance({ id });
    if (!doc) return;
    applyDefinedUpdates(
      doc as unknown as Record<string, unknown>,
      updates as Record<string, unknown>,
    );
    doc.updatedAt = Date.now();
    await doc.save();
  }

  public async deleteCertRequirement(id: number): Promise<void> {
    await this.deleteNumberedDoc(this.CCertRequirement, id);
  }

  public async deleteCertRequirementsByService(serviceId: number): Promise<void> {
    for (const requirement of await this.getCertRequirementsByService(serviceId)) {
      await requirement.delete();
    }
  }

  // ============ DNS Records ============

  public async createDnsRecord(recordArg: Omit<TDnsRecordWithZone, 'id'>): Promise<DnsRecordDoc> {
    const existing = await this.getDnsRecordByDomain(recordArg.domain);
    if (existing?.id) {
      await this.deleteDnsRecord(recordArg.domain);
    }
    return await this.createDoc(DnsRecordDoc, {
      ...recordArg,
      id: await this.getNextNumericId(this.CDnsRecord),
    });
  }

  public async getDnsRecordByDomain(domainArg: string): Promise<DnsRecordDoc | null> {
    return await this.CDnsRecord.getInstance({ domain: domainArg });
  }

  public async getAllDnsRecords(): Promise<DnsRecordDoc[]> {
    return (await this.CDnsRecord.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async deleteDnsRecord(domainArg: string): Promise<void> {
    const record = await this.getDnsRecordByDomain(domainArg);
    if (!record) return;
    await record.delete();
  }

  // ============ Registry Tokens ============

  public async createRegistryToken(token: Omit<IRegistryToken, 'id'>): Promise<RegistryTokenDoc> {
    return await this.createDoc(RegistryTokenDoc, {
      ...token,
      id: await this.getNextNumericId(this.CRegistryToken),
    });
  }

  public async getRegistryTokenById(id: number): Promise<RegistryTokenDoc | null> {
    return await this.CRegistryToken.getInstance({ id });
  }

  public async getRegistryTokenByHash(tokenHash: string): Promise<RegistryTokenDoc | null> {
    return await this.CRegistryToken.getInstance({ tokenHash });
  }

  public async getAllRegistryTokens(): Promise<RegistryTokenDoc[]> {
    return (await this.CRegistryToken.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async getRegistryTokensByType(type: 'global' | 'ci'): Promise<RegistryTokenDoc[]> {
    return (await this.CRegistryToken.getInstances({ type })).sort((a, b) =>
      b.createdAt - a.createdAt
    );
  }

  public async updateRegistryTokenLastUsed(id: number): Promise<void> {
    const doc = await this.CRegistryToken.getInstance({ id });
    if (!doc) return;
    doc.lastUsedAt = Date.now();
    await doc.save();
  }

  public async cleanupRegistryTokensForService(serviceNameArg: string): Promise<void> {
    for (const token of await this.getAllRegistryTokens()) {
      if (!Array.isArray(token.scope) || !token.scope.includes(serviceNameArg)) continue;
      const remainingScope = token.scope.filter((scopeArg) => scopeArg !== serviceNameArg);
      if (remainingScope.length === 0) {
        await token.delete();
      } else {
        token.scope = remainingScope;
        await token.save();
      }
    }
  }

  public async deleteRegistryToken(id: number): Promise<void> {
    await this.deleteNumberedDoc(this.CRegistryToken, id);
  }

  // ============ Platform Services ============

  public async createPlatformService(
    service: Omit<IPlatformService, 'id'>,
  ): Promise<PlatformServiceDoc> {
    const now = Date.now();
    return await this.createDoc(PlatformServiceDoc, {
      ...service,
      id: await this.getNextNumericId(this.CPlatformService),
      createdAt: service.createdAt || now,
      updatedAt: service.updatedAt || now,
    });
  }

  public async getPlatformServiceByName(name: string): Promise<PlatformServiceDoc | null> {
    return await this.CPlatformService.getInstance({ name });
  }

  public async getPlatformServiceById(id: number): Promise<PlatformServiceDoc | null> {
    return await this.CPlatformService.getInstance({ id });
  }

  public async getPlatformServiceByType(
    type: TPlatformServiceType,
  ): Promise<PlatformServiceDoc | null> {
    return await this.CPlatformService.getInstance({ type });
  }

  public async getAllPlatformServices(): Promise<PlatformServiceDoc[]> {
    return (await this.CPlatformService.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async updatePlatformService(
    id: number,
    updates: Partial<IPlatformService>,
  ): Promise<void> {
    const doc = await this.CPlatformService.getInstance({ id });
    if (!doc) return;
    applyDefinedUpdates(
      doc as unknown as Record<string, unknown>,
      updates as Record<string, unknown>,
    );
    doc.updatedAt = Date.now();
    await doc.save();
  }

  public async deletePlatformService(id: number): Promise<void> {
    await this.deleteNumberedDoc(this.CPlatformService, id);
  }

  // ============ Platform Resources ============

  public async createPlatformResource(
    resource: Omit<IPlatformResource, 'id'>,
  ): Promise<PlatformResourceDoc> {
    return await this.createDoc(PlatformResourceDoc, {
      ...resource,
      id: await this.getNextNumericId(this.CPlatformResource),
      createdAt: resource.createdAt || Date.now(),
    });
  }

  public async persistPlatformResourceRecovery(
    resourceArg: Omit<IPlatformResource, 'id'>,
  ): Promise<void> {
    const collection = this.db.mongoDb.collection('PlatformResourceDoc');
    const ownershipSelector = {
      platformServiceId: resourceArg.platformServiceId,
      serviceId: resourceArg.serviceId,
      resourceType: resourceArg.resourceType,
      resourceName: resourceArg.resourceName,
    };
    const existing = await collection.findOne(ownershipSelector);
    if (existing) {
      await collection.updateOne(
        ownershipSelector,
        { $set: {
          credentialsEncrypted: resourceArg.credentialsEncrypted,
          createdAt: resourceArg.createdAt || existing.createdAt || Date.now(),
        } },
        { upsert: false },
      );
      return;
    }
    await collection.insertOne({
      ...resourceArg,
      id: await this.getNextNumericId(this.CPlatformResource),
      createdAt: resourceArg.createdAt || Date.now(),
    });
  }

  public async getPlatformResourceById(id: number): Promise<PlatformResourceDoc | null> {
    return await this.CPlatformResource.getInstance({ id });
  }

  public async getPlatformResourcesByService(serviceId: number): Promise<PlatformResourceDoc[]> {
    return await this.CPlatformResource.getInstances({ serviceId });
  }

  public async getPlatformResourcesByPlatformService(
    platformServiceId: number,
  ): Promise<PlatformResourceDoc[]> {
    return await this.CPlatformResource.getInstances({ platformServiceId });
  }

  public async getAllPlatformResources(): Promise<PlatformResourceDoc[]> {
    return (await this.CPlatformResource.getInstances({})).sort((a, b) =>
      b.createdAt - a.createdAt
    );
  }

  public async deletePlatformResource(id: number): Promise<void> {
    await this.deleteNumberedDoc(this.CPlatformResource, id);
  }

  public async deletePlatformResourcesByService(serviceId: number): Promise<void> {
    for (const resource of await this.getPlatformResourcesByService(serviceId)) {
      await resource.delete();
    }
  }

  // ============ Backups ============

  public async createBackup(backup: Omit<IBackup, 'id'>): Promise<BackupDoc> {
    return await this.createDoc(BackupDoc, {
      ...backup,
      id: await this.getNextNumericId(this.CBackup),
    });
  }

  public async getBackupById(id: number): Promise<BackupDoc | null> {
    return await this.CBackup.getInstance({ id });
  }

  public async getBackupsByService(serviceId: number): Promise<BackupDoc[]> {
    return (await this.CBackup.getInstances({ serviceId })).sort((a, b) =>
      b.createdAt - a.createdAt
    );
  }

  public async getAllBackups(): Promise<BackupDoc[]> {
    return (await this.CBackup.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async deleteBackup(id: number): Promise<void> {
    await this.deleteNumberedDoc(this.CBackup, id);
  }

  public async deleteBackupsByService(serviceId: number): Promise<void> {
    for (const backup of await this.getBackupsByService(serviceId)) {
      await backup.delete();
    }
  }

  public async getBackupsBySchedule(scheduleId: number): Promise<BackupDoc[]> {
    return (await this.CBackup.getInstances({ scheduleId })).sort((a, b) =>
      b.createdAt - a.createdAt
    );
  }

  public async getBackupBySnapshotId(snapshotId: string): Promise<BackupDoc | null> {
    return await this.CBackup.getInstance({ snapshotId });
  }

  // ============ Backup Schedules ============

  public async createBackupSchedule(
    schedule: Omit<IBackupSchedule, 'id'>,
  ): Promise<BackupScheduleDoc> {
    const now = Date.now();
    return await this.createDoc(BackupScheduleDoc, {
      ...schedule,
      id: await this.getNextNumericId(this.CBackupSchedule),
      createdAt: schedule.createdAt || now,
      updatedAt: schedule.updatedAt || now,
    });
  }

  public async getBackupScheduleById(id: number): Promise<BackupScheduleDoc | null> {
    return await this.CBackupSchedule.getInstance({ id });
  }

  public async getBackupSchedulesByService(serviceId: number): Promise<BackupScheduleDoc[]> {
    return (await this.CBackupSchedule.getInstances({ serviceId })).sort((a, b) =>
      b.createdAt - a.createdAt
    );
  }

  public async getEnabledBackupSchedules(): Promise<BackupScheduleDoc[]> {
    return (await this.CBackupSchedule.getInstances({ enabled: true }))
      .sort((a, b) => (a.nextRunAt || 0) - (b.nextRunAt || 0));
  }

  public async getAllBackupSchedules(): Promise<BackupScheduleDoc[]> {
    return (await this.CBackupSchedule.getInstances({})).sort((a, b) => b.createdAt - a.createdAt);
  }

  public async updateBackupSchedule(
    id: number,
    updates: IBackupScheduleUpdate & {
      lastRunAt?: number;
      nextRunAt?: number;
      lastStatus?: 'success' | 'failed' | null;
      lastError?: string | null;
    },
  ): Promise<void> {
    const doc = await this.CBackupSchedule.getInstance({ id });
    if (!doc) return;
    applyDefinedUpdates(
      doc as unknown as Record<string, unknown>,
      updates as Record<string, unknown>,
    );
    doc.updatedAt = Date.now();
    await doc.save();
  }

  public async deleteBackupSchedule(id: number): Promise<void> {
    await this.deleteNumberedDoc(this.CBackupSchedule, id);
  }

  public async deleteBackupSchedulesByService(serviceId: number): Promise<void> {
    for (const schedule of await this.getBackupSchedulesByService(serviceId)) {
      await schedule.delete();
    }
  }
}
