import {
  AHA_SCREEN_EVENT_NAMES,
  sanitizeAuditMetadata,
} from './audit-event-metadata';

describe('audit event metadata policy', () => {
  it('keeps only whitelisted metadata fields for each event', () => {
    const metadata = sanitizeAuditMetadata('api.request.failed', {
      status_code: 500,
      method: 'GET',
      endpoint: '/clients',
      duration_ms: 120,
      route: '/clientes/123/investimentos',
      url: 'https://api.test/clients',
      response_message: 'Internal error',
      response_body: '{"message":"Internal error","trace":"full"}',
      response_body_preview: '{"message":"Internal error"}',
      trace_id: 'trace-1',
      email: 'private@example.com',
      cpf: '12345678900',
    });

    expect(metadata).toEqual({
      status_code: 500,
      method: 'GET',
      endpoint: '/clients',
      duration_ms: 120,
      route: '/clientes/123/investimentos',
      url: 'https://api.test/clients',
      response_message: 'Internal error',
      response_body_preview: '{"message":"Internal error"}',
      trace_id: 'trace-1',
    });
  });

  it('keeps import trigger metadata for succeeded and failed imports', () => {
    expect(
      sanitizeAuditMetadata('import.openfinance.succeeded', {
        client_uuid: 'client-1',
        trigger: 'manual',
        account_id: 'account-1',
      }),
    ).toEqual({
      client_uuid: 'client-1',
      trigger: 'manual',
      account_id: 'account-1',
    });

    expect(
      sanitizeAuditMetadata('import.csv.failed', {
        client_uuid: 'client-1',
        trigger: 'scheduled',
        file_type: 'transactions',
      }),
    ).toEqual({
      client_uuid: 'client-1',
      trigger: 'scheduled',
      file_type: 'transactions',
    });
  });

  it('keeps only non-sensitive usage fields for budget.rule.applied', () => {
    expect(
      sanitizeAuditMetadata('budget.rule.applied', {
        client_uuid: 'client-1',
        match_type: 'CONTAINS',
        matched_count: 3,
        // sensitive fields that must never be stored:
        description: 'Uber - cartao 1234',
        category_name: 'Transporte',
        amount: '199.90',
      }),
    ).toEqual({
      client_uuid: 'client-1',
      match_type: 'CONTAINS',
      matched_count: 3,
    });
  });

  it('removes full response bodies from API failures and UI crashes', () => {
    expect(
      sanitizeAuditMetadata('api.request.failed', {
        status_code: 500,
        response_body: 'full body',
        response_body_preview: 'preview',
      }),
    ).toEqual({
      status_code: 500,
      response_body_preview: 'preview',
    });

    expect(
      sanitizeAuditMetadata('ui.crash', {
        route: '/dashboard',
        response_body: 'full body',
      }),
    ).toEqual({
      route: '/dashboard',
    });
  });

  it('removes response_body keys from nested response errors', () => {
    expect(
      sanitizeAuditMetadata('api.request.failed', {
        response_errors: [
          {
            response_body: 'full',
            nested: {
              response_body: 'full2',
              message: 'kept',
            },
          },
        ],
      }),
    ).toEqual({
      response_errors: [
        {
          nested: {
            message: 'kept',
          },
        },
      ],
    });
  });

  it('redacts sensitive strings and sensitive metadata keys', () => {
    const metadata = sanitizeAuditMetadata('api.request.failed', {
      endpoint:
        '/clients?email=private@example.com&cpf=123.456.789-00&cnpj=12345678000199',
      response_message:
        'Bearer abc.def.ghi failed for password hunter2 and token abc123',
      response_errors: [
        {
          message: 'sent to other@example.com',
          refresh_token: 'refresh-secret',
          nested: {
            senha: 'senha-secreta',
            trace: 'cpf 12345678900',
          },
        },
      ],
    });

    expect(metadata).toEqual({
      endpoint: '/clients?email=[REDACTED]&cpf=[REDACTED]&cnpj=[REDACTED]',
      response_message:
        '[REDACTED] failed for password [REDACTED] and token [REDACTED]',
      response_errors: [
        {
          message: 'sent to [REDACTED]',
          refresh_token: '[REDACTED]',
          nested: {
            senha: '[REDACTED]',
            trace: 'cpf [REDACTED]',
          },
        },
      ],
    });
  });

  it('keeps backend exception trace metadata for API failures with sensitive values redacted', () => {
    const metadata = sanitizeAuditMetadata('api.request.failed', {
      status_code: 500,
      method: 'POST',
      endpoint: '/v1/user/client',
      request_id: 'request-1',
      trace_id: 'trace-1',
      exception_class: 'RuntimeException',
      exception_message:
        'Failed for bearer api-token and user private@example.com',
      exception_file: '/app/Services/SecretService.php',
      exception_line: 42,
      exception_trace:
        '#0 /app/Controller.php(10): token secret-token sent to private@example.com',
      exception_previous: [
        {
          class: 'PDOException',
          message: 'password db-secret failed for cpf 12345678900',
          trace: '#0 /app/Database.php(5)',
        },
      ],
      trace_truncated: false,
    });

    expect(metadata).toEqual({
      status_code: 500,
      method: 'POST',
      endpoint: '/v1/user/client',
      request_id: 'request-1',
      trace_id: 'trace-1',
      exception_class: 'RuntimeException',
      exception_message: 'Failed for [REDACTED] and user [REDACTED]',
      exception_file: '/app/Services/SecretService.php',
      exception_line: 42,
      exception_trace:
        '#0 /app/Controller.php(10): token [REDACTED] sent to [REDACTED]',
      exception_previous: [
        {
          class: 'PDOException',
          message: 'password [REDACTED] failed for cpf [REDACTED]',
          trace: '#0 /app/Database.php(5)',
        },
      ],
      trace_truncated: false,
    });
  });

  it('truncates oversized backend exception traces and marks them as truncated', () => {
    const metadata = sanitizeAuditMetadata('api.request.failed', {
      status_code: 500,
      exception_trace: 'x'.repeat(300_000),
    });

    expect(String(metadata.exception_trace).length).toBeLessThan(300_000);
    expect(metadata.trace_truncated).toBe(true);
  });

  it('uses an empty object for unknown event metadata', () => {
    expect(
      sanitizeAuditMetadata('unknown.event', {
        route: '/dashboard',
        email: 'private@example.com',
      }),
    ).toEqual({});
  });

  it('defines the V1 screen events used by AHA metrics', () => {
    expect(AHA_SCREEN_EVENT_NAMES).toContain('screen.client.loaded');
    expect(AHA_SCREEN_EVENT_NAMES).toContain('screen.debts.loaded');
  });
});
