import * as Sentry from '@sentry/react';

import {ERROR_MAP as origErrorMap} from 'sentry/utils/requestError/requestError';

import {
  addEndpointTagToRequestError,
  initializeSdk,
  isEventWithFileUrl,
  isFilteredRequestErrorEvent,
} from './initializeSdk';

const ERROR_MAP = {
  ...origErrorMap,
  // remove `UndefinedResponseBodyError` since we don't filter those
  200: undefined,
};

describe('initializeSdk', () => {
  beforeAll(() => {
    window.__initialData = {
      ...window.__initialData,
      customerDomain: null,
    };
  });

  // This is a regression test for Sentry incident inc-433
  // We need to make sure that /^\// is included in the list of tracePropagationTargets
  // so that we can have frontend to backend tracing.
  it('enables distributed tracing to sentry api endpoint', () => {
    initializeSdk({
      ...window.__initialData,
      apmSampling: 1,
      sentryConfig: {
        allowUrls: [],
        dsn: '',
        release: '',
        tracePropagationTargets: ['other', 'stuff'],
      },
    });

    expect(Sentry.init).toHaveBeenCalledWith(
      expect.objectContaining({
        tracePropagationTargets: expect.arrayContaining([/^\//]),
      })
    );
  });
});

describe('isFilteredRequestErrorEvent', () => {
  const methods = ['GET', 'POST', 'PUT', 'DELETE'];
  const stati = [200, 400, 401, 403, 404, 429];

  describe('matching error type, matching message', () => {
    for (const method of methods) {
      describe(`${method} requests`, () => {
        for (const status of stati) {
          // We have to filter out falsy values here because 200 isn't in `ERROR_MAP`
          // and will never appear with any other error name besides `RequestError`
          for (const errorName of ['RequestError', ERROR_MAP[status]].filter(Boolean)) {
            describe('main error', () => {
              it(`recognizes ${status} ${method} events of type ${errorName}`, () => {
                const event = {
                  exception: {
                    values: [
                      {type: errorName, value: `${method} /dogs/are/great/ ${status}`},
                    ],
                  },
                };

                expect(isFilteredRequestErrorEvent(event)).toBeTruthy();
              });
            });

            describe('cause error', () => {
              it(`recognizes ${status} ${method} events of type ${errorName} as causes`, () => {
                const event = {
                  exception: {
                    values: [
                      {type: errorName, value: `${method} /dogs/are/great/ ${status}`},
                      {type: 'InsufficientTreatsError', value: 'Not enough treats!'},
                    ],
                  },
                };

                expect(isFilteredRequestErrorEvent(event)).toBeTruthy();
              });
            });
          }
        }
      });
    }
  });

  describe('matching error type, non-matching message', () => {
    for (const status of stati) {
      // We have to filter out falsy values here because 200 isn't in `ERROR_MAP`
      // and will never appear with any other error name besides `RequestError`
      for (const errorName of ['RequestError', ERROR_MAP[status]].filter(Boolean)) {
        describe('main error', () => {
          it(`rejects other errors of type ${errorName}`, () => {
            const event = {
              exception: {
                values: [
                  {type: errorName, value: "Failed to fetch requested object: 'ball'"},
                ],
              },
            };

            expect(isFilteredRequestErrorEvent(event)).toBeFalsy();
          });
        });

        describe('cause error', () => {
          it(`rejects other errors of type ${errorName} as causes`, () => {
            const event = {
              exception: {
                values: [
                  {type: errorName, value: "Failed to fetch requested object: 'ball'"},
                  {type: 'InsufficientTreatsError', value: 'Not enough treats!'},
                ],
              },
            };

            expect(isFilteredRequestErrorEvent(event)).toBeFalsy();
          });
        });
      }
    }
  });

  describe('non-matching error type, non-matching message', () => {
    it(`rejects other errors`, () => {
      const event = {
        exception: {
          values: [{type: 'UncaughtSquirrelError', value: 'Squirrel was not caught'}],
        },
      };

      expect(isFilteredRequestErrorEvent(event)).toBeFalsy();
    });

    it(`rejects other errors as causes`, () => {
      const event = {
        exception: {
          values: [
            {type: 'UncaughtSquirrelError', value: 'Squirrel was not caught'},
            {type: 'InsufficientTreatsError', value: 'Not enough treats!'},
          ],
        },
      };

      expect(isFilteredRequestErrorEvent(event)).toBeFalsy();
    });
  });
});

describe('isEventWithFileUrl', () => {
  it('recognizes events with `file://` urls', () => {
    const event = {request: {url: 'file://dogs/are/great.html'}};

    expect(isEventWithFileUrl(event)).toBeTruthy();
  });

  it('rejects events with other urls', () => {
    const event = {request: {url: 'http://dogs.are.great'}};

    expect(isEventWithFileUrl(event)).toBeFalsy();
  });

  it('rejects events without urls', () => {
    const event = {};

    expect(isEventWithFileUrl(event)).toBeFalsy();
  });
});

describe('addEndpointTagToRequestError', () => {
  it('adds `endpoint` tag to events with matching message`', () => {
    const event = {
      exception: {
        values: [{type: 'RequestError', value: 'GET /dogs/are/great/ 500'}],
      },
      tags: {},
    };

    addEndpointTagToRequestError(event);

    expect(event.tags).toEqual({
      endpoint: 'GET /dogs/are/great/',
    });
  });

  it("doesn't add `endpoint` tag to events with non-matching message", () => {
    const nonmatchingMessages = [
      'RequestError with no endpoint for some reason',
      'Some other stuff is wrong with endpoint /dogs/are/great/',
      'This error has nothing to do with requests or endpoints at all',
    ];

    for (const msg of nonmatchingMessages) {
      const event = {
        exception: {
          values: [{type: 'RequestError', value: msg}],
        },
        tags: {},
      };

      addEndpointTagToRequestError(event);

      expect(event.tags).toEqual({});
    }
  });

  it("doesn't add `endpoint` tag to events with no exception", () => {
    const event = {
      tags: {},
    };

    addEndpointTagToRequestError(event);

    expect(event.tags).toEqual({});
  });
});