import type { OneboxDatabase } from '../ts/database/index.js';
import * as plugins from '../ts/plugins.js';
import { credentialEncryption } from '../ts/classes/encryption.js';
import { logger } from '../ts/logging.js';
import { getErrorMessage } from '../ts/utils/error.js';

const legacyType = 'redis';
const legacyContainerName = 'onebox-redis';
const valkeyType = 'valkey';
const valkeyContainerName = 'onebox-valkey';

const replaceLegacyConnectionValues = (credentialsArg: Record<string, string>): Record<string, string> => {
  const credentials = { ...credentialsArg };
  if (credentials.host === legacyContainerName) {
    credentials.host = valkeyContainerName;
  }
  if (credentials.connectionString) {
    credentials.connectionString = credentials.connectionString
      .replace(/^redis:\/\//, 'valkey://')
      .replaceAll(legacyContainerName, valkeyContainerName);
  }
  return credentials;
};

const getPlatformDataDir = async (
  databaseArg: OneboxDatabase,
  serviceDirectoryArg: string,
): Promise<string> => {
  const configuredDataDir = await databaseArg.getSetting('dataDir');
  const baseDataDir = configuredDataDir ||
    (process.env.ONEBOX_DEV === 'true' ? './.nogit/platform-data' : '/var/lib/onebox');
  const absoluteBaseDataDir = baseDataDir.startsWith('/') ? baseDataDir : plugins.path.join(process.cwd(), baseDataDir);
  return `${absoluteBaseDataDir.replace(/\/+$/, '')}/${serviceDirectoryArg}`;
};

const moveLegacyDataDir = async (databaseArg: OneboxDatabase): Promise<void> => {
  const legacyDataDir = await getPlatformDataDir(databaseArg, legacyType);
  const valkeyDataDir = await getPlatformDataDir(databaseArg, valkeyType);

  try {
    const legacyStat = await plugins.fs.stat(legacyDataDir);
    if (!legacyStat.isDirectory) return;
  } catch {
    return;
  }

  try {
    await plugins.fs.stat(valkeyDataDir);
    return;
  } catch {
    // Target does not exist yet, so the legacy directory can be renamed.
  }

  try {
    await plugins.fs.rename(legacyDataDir, valkeyDataDir);
    logger.info(`Moved legacy platform data from ${legacyDataDir} to ${valkeyDataDir}`);
  } catch (error) {
    logger.warn(`Failed to move legacy platform data to Valkey directory: ${getErrorMessage(error)}`);
  }
};

export const runValkeyPlatformRenameMigration = async (
  databaseArg: OneboxDatabase,
): Promise<void> => {
  await moveLegacyDataDir(databaseArg);

  const services = await databaseArg.CService.getInstances({});
  for (const service of services) {
    const requirements = service.platformRequirements as Record<string, boolean> | undefined;
    if (!requirements?.[legacyType]) continue;
    service.platformRequirements = {
      ...requirements,
      [valkeyType]: requirements[legacyType],
      [legacyType]: undefined,
    } as typeof service.platformRequirements;
    delete (service.platformRequirements as Record<string, boolean | undefined>)[legacyType];
    service.updatedAt = Date.now();
    await service.save();
  }

  const platformServices = await databaseArg.CPlatformService.getInstances({});
  const legacyPlatformServices = platformServices.filter((platformServiceArg) => {
    return (platformServiceArg.type as string) === legacyType;
  });

  for (const platformService of legacyPlatformServices) {
    platformService.type = valkeyType as typeof platformService.type;
    platformService.name = platformService.name === legacyContainerName
      ? valkeyContainerName
      : platformService.name.replaceAll(legacyType, valkeyType);
    platformService.status = 'stopped';
    platformService.containerId = undefined;
    platformService.config = {
      ...platformService.config,
      image: 'valkey/valkey:9.1.0-alpine',
      volumes: (platformService.config.volumes || []).map((volumeArg) => {
        return volumeArg.replaceAll(legacyType, valkeyType);
      }),
    };
    platformService.updatedAt = Date.now();
    await platformService.save();
  }

  for (const platformService of legacyPlatformServices) {
    const resources = await databaseArg.CPlatformResource.getInstances({
      platformServiceId: platformService.id,
    });
    for (const resource of resources) {
      try {
        const credentials = await credentialEncryption.decrypt(resource.credentialsEncrypted);
        const nextCredentials = replaceLegacyConnectionValues(credentials);
        resource.credentialsEncrypted = await credentialEncryption.encrypt(nextCredentials);
        await resource.save();
      } catch (error) {
        logger.warn(`Failed to rewrite Valkey resource credentials for ${resource.resourceName}: ${getErrorMessage(error)}`);
      }
    }
  }

  const backups = await databaseArg.CBackup.getInstances({});
  for (const backup of backups) {
    if (!backup.platformResources?.includes(legacyType as typeof backup.platformResources[number])) continue;
    backup.platformResources = backup.platformResources.map((platformResourceArg) => {
      return (platformResourceArg as string) === legacyType
        ? valkeyType as typeof platformResourceArg
        : platformResourceArg;
    });
    await backup.save();
  }
};
