// Sentry init wrapper — guarantees graceful no-op when SENTRY_DSN is
// absent, scrubs PII on send when configured, and exposes a usable
// surface to the rest of the codebase regardless of init state.

// Mock @sentry/node BEFORE requiring our wrapper so we control what
// init() and setupExpressErrorHandler do.
const mockSentryInit = jest.fn();
const mockSentryCapture = jest.fn();
const mockSentryHandler = jest.fn();
jest.mock('@sentry/node', () => ({
  init: (...a) => mockSentryInit(...a),
  captureException: (...a) => mockSentryCapture(...a),
  captureMessage: jest.fn(),
  setupExpressErrorHandler: (...a) => mockSentryHandler(...a),
  addBreadcrumb: jest.fn(),
  setUser: jest.fn(),
  setTag: jest.fn(),
  setContext: jest.fn(),
}));

const sentryWrapper = require('../../src/utils/sentry');

beforeEach(() => {
  mockSentryInit.mockReset();
  mockSentryCapture.mockReset();
  mockSentryHandler.mockReset();
  sentryWrapper.__internals.__resetForTests();
  delete process.env.SENTRY_DSN;
});

describe('initSentry', () => {
  test('no-op when SENTRY_DSN is unset (sentry.init NOT called)', () => {
    sentryWrapper.initSentry();
    expect(mockSentryInit).not.toHaveBeenCalled();
    expect(sentryWrapper.isInitialized()).toBe(false);
  });

  test('initializes when SENTRY_DSN is set', () => {
    process.env.SENTRY_DSN = 'https://abc@sentry.io/123';
    sentryWrapper.initSentry();
    expect(mockSentryInit).toHaveBeenCalledTimes(1);
    expect(sentryWrapper.isInitialized()).toBe(true);
    const cfg = mockSentryInit.mock.calls[0][0];
    expect(cfg.dsn).toBe('https://abc@sentry.io/123');
    expect(cfg.tracesSampleRate).toBe(0.1);
    expect(cfg.sendDefaultPii).toBe(false);
    expect(typeof cfg.beforeSend).toBe('function');
  });

  test('explicit dsn arg overrides env', () => {
    process.env.SENTRY_DSN = 'env-dsn';
    sentryWrapper.initSentry({ dsn: 'arg-dsn' });
    expect(mockSentryInit.mock.calls[0][0].dsn).toBe('arg-dsn');
  });

  test('idempotent — second call is a no-op', () => {
    process.env.SENTRY_DSN = 'https://abc@sentry.io/123';
    sentryWrapper.initSentry();
    sentryWrapper.initSentry();
    expect(mockSentryInit).toHaveBeenCalledTimes(1);
  });
});

describe('beforeSend PII scrubbing', () => {
  beforeEach(() => {
    process.env.SENTRY_DSN = 'https://abc@sentry.io/123';
    sentryWrapper.initSentry();
  });

  test('strips user.ip_address and user.email', () => {
    const cfg = mockSentryInit.mock.calls[0][0];
    const event = { user: { id: 'u1', ip_address: '1.2.3.4', email: 'a@b.com' } };
    const scrubbed = cfg.beforeSend(event);
    expect(scrubbed.user.id).toBe('u1');
    expect(scrubbed.user.ip_address).toBeUndefined();
    expect(scrubbed.user.email).toBeUndefined();
  });

  test('strips cookies + authorization + internal-key headers from request', () => {
    const cfg = mockSentryInit.mock.calls[0][0];
    const event = {
      request: {
        cookies: { session: 'abc' },
        headers: {
          authorization: 'Bearer secret',
          cookie: 'session=abc',
          'x-internal-key': 'leak',
          'x-vyndr-internal-key': 'leak',
          'content-type': 'application/json',
        },
      },
    };
    const scrubbed = cfg.beforeSend(event);
    expect(scrubbed.request.cookies).toBeUndefined();
    expect(scrubbed.request.headers.authorization).toBeUndefined();
    expect(scrubbed.request.headers.cookie).toBeUndefined();
    expect(scrubbed.request.headers['x-internal-key']).toBeUndefined();
    expect(scrubbed.request.headers['x-vyndr-internal-key']).toBeUndefined();
    // Non-sensitive headers are kept.
    expect(scrubbed.request.headers['content-type']).toBe('application/json');
  });
});

describe('noop client surface', () => {
  test('captureException is callable without DSN (no throw)', () => {
    sentryWrapper.initSentry();
    expect(() => sentryWrapper.Sentry.captureException(new Error('test'))).not.toThrow();
    expect(mockSentryCapture).not.toHaveBeenCalled();
  });

  test('setupExpressErrorHandler is callable without DSN (no throw)', () => {
    sentryWrapper.initSentry();
    expect(() => sentryWrapper.Sentry.setupExpressErrorHandler({})).not.toThrow();
    expect(mockSentryHandler).not.toHaveBeenCalled();
  });

  test('after init, captureException routes to real Sentry', () => {
    process.env.SENTRY_DSN = 'https://abc@sentry.io/123';
    sentryWrapper.initSentry();
    const err = new Error('boom');
    sentryWrapper.Sentry.captureException(err);
    expect(mockSentryCapture).toHaveBeenCalledWith(err);
  });

  test('after init, setupExpressErrorHandler delegates to real Sentry', () => {
    process.env.SENTRY_DSN = 'https://abc@sentry.io/123';
    sentryWrapper.initSentry();
    const app = {};
    sentryWrapper.Sentry.setupExpressErrorHandler(app);
    expect(mockSentryHandler).toHaveBeenCalledWith(app);
  });
});
