import type { OneboxDatabase } from '../ts/database/index.js';

const controlTokenKey = 'SERVEZONE_APP_CONTROL_TOKEN';

const requirePositiveServiceId = (valueArg: unknown): number => {
  if (!Number.isSafeInteger(valueArg) || (valueArg as number) < 1) {
    throw new Error('Legacy hosted-app lifecycle has an invalid serviceId');
  }
  return valueArg as number;
};

const requireNonEmptyString = (valueArg: unknown, labelArg: string): string => {
  if (typeof valueArg !== 'string' || valueArg.length === 0) {
    throw new Error(`Legacy hosted-app lifecycle has an invalid ${labelArg}`);
  }
  return valueArg;
};

export const runRuntimeSecretExtractionMigration = async (
  databaseArg: OneboxDatabase,
): Promise<void> => {
  const lifecycleCollection = databaseArg.db.mongoDb.collection('HostedAppLifecycleDoc');
  const legacyRecords = await lifecycleCollection.find({
    controlToken: { $exists: true },
  }).toArray();

  for (const legacyRecord of legacyRecords) {
    const serviceId = requirePositiveServiceId(legacyRecord.serviceId);
    const appInstanceId = requireNonEmptyString(legacyRecord.appInstanceId, 'appInstanceId');
    const controlToken = requireNonEmptyString(legacyRecord.controlToken, 'controlToken');
    const service = await databaseArg.CService.getInstance({ id: serviceId });
    if (!service) {
      throw new Error(`Legacy hosted-app lifecycle references missing service ${serviceId}`);
    }

    const currentSecrets = await databaseArg.serviceSecrets.get(serviceId);
    const persistedToken = currentSecrets[controlTokenKey];
    const publicToken = service.envVars?.[controlTokenKey];
    if (persistedToken && persistedToken !== controlToken) {
      throw new Error(`Hosted-app control token conflict for service ${serviceId}`);
    }
    if (publicToken && publicToken !== controlToken) {
      throw new Error(`Public hosted-app control token conflict for service ${serviceId}`);
    }

    await databaseArg.serviceSecrets.set(serviceId, {
      ...currentSecrets,
      [controlTokenKey]: controlToken,
    });

    if (service.envVars && controlTokenKey in service.envVars) {
      service.envVars = { ...service.envVars };
      delete service.envVars[controlTokenKey];
      service.updatedAt = Date.now();
      await service.save();
    }

    const result = await lifecycleCollection.updateOne(
      { serviceId, appInstanceId, controlToken },
      { $unset: { controlToken: '' } },
      { upsert: false },
    );
    if (result.matchedCount !== 1) {
      throw new Error(`Hosted-app control token changed during migration for service ${serviceId}`);
    }
  }

  for (const service of await databaseArg.CService.getInstances({})) {
    const publicToken = service.envVars?.[controlTokenKey];
    if (typeof publicToken !== 'string' || publicToken.length === 0) continue;
    const currentSecrets = await databaseArg.serviceSecrets.get(service.id);
    const persistedToken = currentSecrets[controlTokenKey];
    if (persistedToken && persistedToken !== publicToken) {
      throw new Error(`Public hosted-app control token conflict for service ${service.id}`);
    }
    await databaseArg.serviceSecrets.set(service.id, {
      ...currentSecrets,
      [controlTokenKey]: publicToken,
    });
    service.envVars = { ...service.envVars };
    delete service.envVars[controlTokenKey];
    service.updatedAt = Date.now();
    await service.save();
  }
};
