import { credentialEncryption } from '../classes/encryption.js';

export interface ISecretSettingsStore {
  getSetting(key: string): Promise<string | null> | string | null;
  setSetting(key: string, value: string): Promise<void> | void;
  deleteSetting(key: string): Promise<void> | void;
  getRawSecretSetting?: (key: string) => Promise<string | null> | string | null;
  setRawSecretSetting?: (key: string, value: string) => Promise<void> | void;
  deleteRawSecretSetting?: (key: string) => Promise<void> | void;
}

interface ILegacySecretSettingsStore {
  getSecretSetting?: (key: string) => Promise<string | null> | string | null;
  setSecretSetting?: (key: string, value: string) => Promise<void> | void;
  deleteSecretSetting?: (key: string) => Promise<void> | void;
}

const encryptedSecretPrefix = 'enc:v1:';

const secretSettingAliases = {
  backupPassword: ['backup_encryption_password'],
  cloudflareToken: ['cloudflareAPIKey'],
  dcrouterGatewayApiToken: ['externalGatewayApiToken'],
  dcrouterManagedAdminApiToken: [],
  coretrafficAdminToken: ['smartproxyAdminToken'],
} as const;

type TCanonicalSecretSettingKey = keyof typeof secretSettingAliases;

export class SecretSettingsManager {
  constructor(private store: ISecretSettingsStore) {}

  public isSecretKey(key: string): boolean {
    return this.resolveCanonicalKey(key) !== null;
  }

  public getCanonicalKeys(): TCanonicalSecretSettingKey[] {
    return Object.keys(secretSettingAliases) as TCanonicalSecretSettingKey[];
  }

  public async get(key: string): Promise<string | null> {
    const canonicalKey = this.resolveCanonicalKey(key);
    if (!canonicalKey) {
      return null;
    }

    for (const candidateKey of this.getCandidateKeys(canonicalKey)) {
      const secretValue = await this.getStoredSecret(candidateKey);
      if (secretValue !== null) {
        const decryptedValue = await this.decodeStoredValue(secretValue);
        await this.normalizeStoredSecret(canonicalKey, candidateKey, secretValue, decryptedValue);
        return decryptedValue;
      }

      const legacyValue = await this.store.getSetting(candidateKey);
      if (legacyValue !== null) {
        await this.set(canonicalKey, legacyValue);
        if (candidateKey !== canonicalKey) {
          await this.store.deleteSetting(candidateKey);
        }
        await this.store.deleteSetting(canonicalKey);
        return legacyValue;
      }
    }

    return null;
  }

  public async set(key: string, value: string | null): Promise<void> {
    const canonicalKey = this.resolveCanonicalKey(key);
    if (!canonicalKey) {
      throw new Error(`Unsupported secret setting key: ${key}`);
    }

    if (!value) {
      await this.clear(canonicalKey);
      return;
    }

    const encryptedValue = await credentialEncryption.encrypt({ value });
    await this.setStoredSecret(canonicalKey, `${encryptedSecretPrefix}${encryptedValue}`);

    for (const aliasKey of secretSettingAliases[canonicalKey]) {
      await this.deleteStoredSecret(aliasKey);
      await this.store.deleteSetting(aliasKey);
    }

    await this.store.deleteSetting(canonicalKey);
  }

  public async has(key: string): Promise<boolean> {
    return (await this.get(key)) !== null;
  }

  public async clear(key: string): Promise<void> {
    const canonicalKey = this.resolveCanonicalKey(key);
    if (!canonicalKey) {
      return;
    }

    await this.deleteStoredSecret(canonicalKey);
    await this.store.deleteSetting(canonicalKey);

    for (const aliasKey of secretSettingAliases[canonicalKey]) {
      await this.deleteStoredSecret(aliasKey);
      await this.store.deleteSetting(aliasKey);
    }
  }

  private resolveCanonicalKey(key: string): TCanonicalSecretSettingKey | null {
    if (key in secretSettingAliases) {
      return key as TCanonicalSecretSettingKey;
    }

    for (const [canonicalKey, aliases] of Object.entries(secretSettingAliases)) {
      if ((aliases as readonly string[]).includes(key)) {
        return canonicalKey as TCanonicalSecretSettingKey;
      }
    }

    return null;
  }

  private getCandidateKeys(canonicalKey: TCanonicalSecretSettingKey): string[] {
    return [canonicalKey, ...secretSettingAliases[canonicalKey]];
  }

  private async getStoredSecret(key: string): Promise<string | null> {
    if (this.store.getRawSecretSetting) {
      return await this.store.getRawSecretSetting(key);
    }
    return await ((this.store as ILegacySecretSettingsStore).getSecretSetting?.(key) ?? null);
  }

  private async setStoredSecret(key: string, value: string): Promise<void> {
    if (this.store.setRawSecretSetting) {
      await this.store.setRawSecretSetting(key, value);
      return;
    }
    await (this.store as ILegacySecretSettingsStore).setSecretSetting?.(key, value);
  }

  private async deleteStoredSecret(key: string): Promise<void> {
    if (this.store.deleteRawSecretSetting) {
      await this.store.deleteRawSecretSetting(key);
      return;
    }
    await (this.store as ILegacySecretSettingsStore).deleteSecretSetting?.(key);
  }

  private async decodeStoredValue(value: string): Promise<string> {
    if (value.startsWith(encryptedSecretPrefix)) {
      const decrypted = await credentialEncryption.decrypt<{ value: string }>(
        value.slice(encryptedSecretPrefix.length),
      );
      return decrypted.value;
    }

    // Compatibility for any earlier secret_settings rows stored without encryption.
    return value;
  }

  private async normalizeStoredSecret(
    canonicalKey: TCanonicalSecretSettingKey,
    sourceKey: string,
    storedValue: string,
    decryptedValue: string,
  ): Promise<void> {
    if (sourceKey !== canonicalKey || !storedValue.startsWith(encryptedSecretPrefix)) {
      await this.set(canonicalKey, decryptedValue);
      if (sourceKey !== canonicalKey) {
        await this.deleteStoredSecret(sourceKey);
      }
    }

    await this.store.deleteSetting(canonicalKey);
    for (const aliasKey of secretSettingAliases[canonicalKey]) {
      await this.store.deleteSetting(aliasKey);
    }
  }
}
