/**
 * Forbids spreading the entire `credentials` object into a cache-key hash input
 * inside `cache.ts`.
 *
 * Bad:
 *   crc32(JSON.stringify({ ...params, ...credentials }))
 *
 * Good (smartsheet/google-drive/freshservice pattern):
 *   if (!credentials.unitoCredentialId) {
 *     throw new HttpErrors.UnprocessableEntityError('...');
 *   }
 *   hashValue({ ...params, userIdentifier: credentials.unitoCredentialId });
 *
 * `unitoCredentialId` is Unito's internal record ID for the credential row — a
 * non-secret UUID. Hashing it never puts secret material in memory. Do NOT
 * fall back to `accessToken`.
 *
 * Why:
 *   1. Cache thrash on credential rotation. Access tokens rotate on every OAuth
 *      refresh (and refresh tokens themselves rotate on use for some providers);
 *      spreading the full credential makes EVERY field a cache-key input, so a
 *      single-field rotation invalidates the whole cache.
 *   2. Credential surface area. The full credential transits memory as a JSON
 *      string before hashing. Any error in this path can surface in stack
 *      traces or memory dumps.
 *   3. Wrong unit of identity. Cache should be keyed on the stable identifier
 *      of the credential (`unitoCredentialId`), not the credential material.
 *
 * Scope:
 *   Only fires in files whose path ends in `/src/cache.ts`. `credentials.ts`
 *   has legitimate `...credentials` spread (enrichment patterns) and is never
 *   affected.
 */
import type { Rule } from 'eslint';

const CACHE_FILE_RE = /\/src\/cache\.ts$/;
const CREDENTIALS_IDENTIFIER_RE = /^cred(|s|entials)$/;

const rule: Rule.RuleModule = {
  meta: {
    type: 'problem',
    docs: {
      description: 'Disallow spreading the entire `credentials` object into a cache-key hash input inside `cache.ts`',
      recommended: true,
    },
    schema: [],
    messages: {
      credentialsSpread:
        'Do not spread `...{{name}}` into a cache-key hash input. Spreading the full credential makes every field (including rotating tokens) part of the cache key, causes cache thrash on token refresh, and surfaces credential material as a JSON string. Use a non-secret stable identifier instead, e.g. `credentials.unitoCredentialId` (throw if it is missing — never fall back to `accessToken`).',
    },
  },

  create(context) {
    const filename = context.filename ?? '';
    if (!CACHE_FILE_RE.test(filename)) {
      return {};
    }

    return {
      SpreadElement(node) {
        if (!node.argument || node.argument.type !== 'Identifier') {
          return;
        }
        if (!CREDENTIALS_IDENTIFIER_RE.test(node.argument.name)) {
          return;
        }
        context.report({
          node,
          messageId: 'credentialsSpread',
          data: { name: node.argument.name },
        });
      },
    };
  },
};

export default rule;
