import { AuditAction } from './entities/audit-log.entity';
import { INTENTIONALLY_UNEMITTED_ACTIONS } from './intentionally-unemitted-actions';
import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';

/**
 * Structural gate: every `AuditAction` enum member must either be emitted somewhere in
 * `src/` (an `AuditAction.<NAME>` literal at a real call site) or be registered in
 * `INTENTIONALLY_UNEMITTED_ACTIONS` with a reason. This keeps the audit vocabulary honest —
 * an enum value that implies an audited event cannot silently exist with no emission site,
 * and new values cannot be added without either wiring them or documenting why they are held back.
 */
const SRC_DIR = join(__dirname, '..', '..');

function listTsFiles(dir: string, out: string[] = []): string[] {
  for (const ent of readdirSync(dir, { withFileTypes: true })) {
    if (ent.name === 'node_modules') continue;
    const full = join(dir, ent.name);
    if (ent.isDirectory()) {
      listTsFiles(full, out);
    } else if (ent.name.endsWith('.ts')) {
      out.push(full);
    }
  }
  return out;
}

const isExcluded = (file: string): boolean =>
  file.endsWith('.spec.ts') ||
  file.endsWith('.test.ts') ||
  file.endsWith('audit-coverage.spec.ts') ||
  file.endsWith('audit-log.entity.ts') ||
  file.endsWith('intentionally-unemitted-actions.ts');

const sourceBodies = listTsFiles(SRC_DIR)
  .filter(file => !isExcluded(file))
  .map(file => readFileSync(file, 'utf8'));

const memberKeys = Object.keys(AuditAction) as (keyof typeof AuditAction)[];

const isEmitted = (key: keyof typeof AuditAction | undefined): boolean =>
  key !== undefined && sourceBodies.some(body => body.includes(`AuditAction.${key}`));

const memberKeyForValue = (value: string): keyof typeof AuditAction | undefined =>
  memberKeys.find(k => AuditAction[k] === (value as AuditAction));

describe('audit action emit coverage', () => {
  it.each(memberKeys)('AuditAction.%s is emitted somewhere in src/ or registered as intentionally unemitted', key => {
    const registered = Boolean(INTENTIONALLY_UNEMITTED_ACTIONS[AuditAction[key]]);
    expect(isEmitted(key) || registered).toBe(true);
  });

  it('intentionally-unemitted registry has no stale entries (none is actually emitted)', () => {
    const stale = Object.keys(INTENTIONALLY_UNEMITTED_ACTIONS)
      .map(value => memberKeyForValue(value))
      .filter((key): key is keyof typeof AuditAction => Boolean(key) && isEmitted(key));
    expect(stale).toEqual([]);
  });

  it('intentionally-unemitted registry entries each carry a non-empty reason', () => {
    const empties = Object.entries(INTENTIONALLY_UNEMITTED_ACTIONS)
      .filter(([, reason]) => typeof reason !== 'string' || reason.trim().length === 0)
      .map(([value]) => value);
    expect(empties).toEqual([]);
  });
});
