import { captureConsoleOutput } from './testHelpers';

import { Logger, LogLevel, LogMessage, LogOutput } from 'tachyon-logger/logger';

import { consoleLogger } from 'tachyon-logger/consoleLogger';
import { logger } from 'tachyon-logger/defaultLogger';

import omit from 'lodash.omit';
import {
  registerSentinelLogger,
  setOnErrorHandler,
} from 'tachyon-logger/clientLogger';
import { ConsoleLogger } from 'tachyon-logger/consoleLogger';
import {
  eventLoggerMiddleware,
  flattenMessageLoggerMiddleware,
  singleStringLoggerMiddleware,
} from 'tachyon-logger/loggerMiddleware';
// import fetchMock from 'fetch-mock';
import {
  SENTINEL_ENDPOINT,
  SENTRY_PLATFORM,
  SENTRY_PRODUCT,
} from 'tachyon-logger/sentinelLogger';

export const browserLogger: Logger = new ConsoleLogger([
  singleStringLoggerMiddleware,
  flattenMessageLoggerMiddleware,
  eventLoggerMiddleware,
]);

export const cloudWatchLogger: Logger = new ConsoleLogger(
  [
    singleStringLoggerMiddleware,
    flattenMessageLoggerMiddleware,
    eventLoggerMiddleware,
  ],
  JSON.stringify,
);

describe('logger', () => {
  interface LogTestLevels {
    logMethod: (message?: any, ...optionalParams: any[]) => void;
    level: LogLevel;
    isWindowDefined: boolean;
  }

  const LOG_TEST_LEVELS: LogTestLevels[] = [
    {
      isWindowDefined: false,
      level: LogLevel.DEBUG,
      logMethod: cloudWatchLogger.debug,
    },
    {
      isWindowDefined: false,
      level: LogLevel.DEBUG,
      logMethod: cloudWatchLogger.log,
    },
    {
      isWindowDefined: false,
      level: LogLevel.INFO,
      logMethod: cloudWatchLogger.info,
    },
    {
      isWindowDefined: false,
      level: LogLevel.WARNING,
      logMethod: cloudWatchLogger.warn,
    },
    {
      isWindowDefined: false,
      level: LogLevel.ERROR,
      logMethod: cloudWatchLogger.error,
    },
    {
      isWindowDefined: true,
      level: LogLevel.DEBUG,
      logMethod: browserLogger.debug,
    },
    {
      isWindowDefined: true,
      level: LogLevel.DEBUG,
      logMethod: browserLogger.log,
    },
    {
      isWindowDefined: true,
      level: LogLevel.INFO,
      logMethod: browserLogger.info,
    },
    {
      isWindowDefined: true,
      level: LogLevel.WARNING,
      logMethod: browserLogger.warn,
    },
    {
      isWindowDefined: true,
      level: LogLevel.ERROR,
      logMethod: browserLogger.error,
    },
  ];

  LOG_TEST_LEVELS.forEach(({ logMethod, level, isWindowDefined }) => {
    function captureLog(func: () => any): LogOutput {
      const output = captureConsoleOutput(func)[0];
      const parsedOutput = isWindowDefined ? output : JSON.parse(output);
      return omit(parsedOutput, 'time') as LogOutput;
    }

    const log: (message: LogMessage) => void = logMethod.bind(
      isWindowDefined ? browserLogger : cloudWatchLogger,
    );

    it('outputs a single log object with message and level', () => {
      expect(captureLog(() => log('test'))).toEqual({
        level,
        message: 'test',
      });
    });

    it('combines all lists of strings into a single message', () => {
      expect(captureLog(() => log(['test1', 'test2', 'test3']))).toEqual({
        level,
        message: 'test1 test2 test3',
      });
    });

    it('stores objects in message', () => {
      expect(captureLog(() => log({ a: 1, b: 2, c: 3 }))).toEqual({
        level,
        message: { a: 1, b: 2, c: 3 },
      });
    });

    it('replaces message with message.message', () => {
      expect(
        captureLog(() => log({ message: 'a message', properties: {} })),
      ).toEqual({ message: 'a message', properties: {}, level });
    });

    it('replaces message with events', () => {
      expect(
        captureLog(() => log({ event: 'anEvent', properties: {} })),
      ).toEqual({
        level,
        event: 'anEvent',
        properties: {},
      });
    });
  });
});

describe('logger middleware singleStringLoggerMiddleware', () => {
  it('combines all lists of strings into a single message', () => {
    expect(
      singleStringLoggerMiddleware({
        level: LogLevel.INFO,
        message: ['test1', 'test2', 'test3'],
      }),
    ).toEqual({
      level: LogLevel.INFO,
      message: 'test1 test2 test3',
    });
  });
});

describe('logger middleware flattenMessageLoggerMiddleware', () => {
  it('replaces message with message.message', () => {
    expect(
      flattenMessageLoggerMiddleware({
        level: LogLevel.INFO,
        message: {
          message: 'a message',
          properties: {},
        },
      }),
    ).toEqual({ message: 'a message', properties: {}, level: LogLevel.INFO });
  });
});

describe('logger middleware eventLoggerMiddleware', () => {
  it('replaces message with events', () => {
    expect(
      eventLoggerMiddleware({
        level: LogLevel.INFO,
        message: { event: 'anEvent', properties: {} },
      }),
    ).toEqual({
      event: 'anEvent',
      level: LogLevel.INFO,
      properties: {},
    });
  });
});

describe('window.onerror logs', () => {
  (window && window.onerror ? it : xit)(
    'an error when an unhandled error occurs',
    () => {
      const error = Error('error');
      const logOutput = captureConsoleOutput(() => {
        try {
          throw error;
        } catch (e) {
          throw window.onerror;
          window.onerror.call(window, e.toString(), 'unit test', 1, 1, e);
        }
      })[0];
      expect(omit(logOutput, 'time')).toEqual({
        error,
        colno: 1,
        level: LogLevel.ERROR,
        lineno: 1,
        message: error.toString(),
        source: 'unit test',
      });
    },
  );
});

/*describe('SentinelLogger', () => {
  beforeEach(() => {
    fetchMock.mock(SENTINEL_ENDPOINT, 200);
    registerSentinelLogger("build", "device", "page");
  });
  afterEach(() => {
    fetchMock.restore();
    logger.setLoggers([consoleLogger]);
  });
  it('sends a report of client-side logs to sentinel', () => {
    logger.error('An error has occurred.');
    expect(fetchMock.called(SENTINEL_ENDPOINT)).toBeTruthy();
    const sentinelRequest = fetchMock.calls(SENTINEL_ENDPOINT)[0][1];
    expect(sentinelRequest.method).toEqual('POST');
    expect(sentinelRequest.headers).toEqual({
      'Content-Type': 'application/json; charset=UTF-8',
      Accept: 'application/json; charset=UTF-8',
    });
    const requestBody = JSON.parse((sentinelRequest as any).body);
    expect(requestBody.buildId).toEqual('test-hash');
    expect(requestBody.clientTime).toBeTruthy();
    expect(requestBody.deviceId).toEqual('test-device-id');
    expect(omit(requestBody.logEntries[0], 'time')).toEqual({
      level: LogLevel.ERROR,
      message: 'An error has occurred.',
    });
    expect(requestBody.logEntries[0].time).toBeTruthy();
    expect(requestBody.pageComponentName).toEqual('');
    expect(requestBody.pageSessionId).toEqual('test-session-id');
    expect(requestBody.platform).toEqual(SENTRY_PLATFORM);
    expect(requestBody.product).toEqual(SENTRY_PRODUCT);
    expect(requestBody.userAgent).toBeTruthy();
    expect(requestBody.userId).toEqual(0);
    expect(requestBody.username).toEqual('');
  });
});*/
