import { bold, cyan, green, red, yellow } from 'chalk';
import { table } from 'table';

import {
  TABLE_HEADER_WITH_WARNINGS_DATA,
  TABLE_HEADER_WITHOUT_WARNINGS_DATA,
} from './constants';
import {
  MockedAggregatedResultWithoutCoverage,
  MockedAggregatedResultWithoutCoverage_multipleResultState,
  MockedAggregatedResultWithoutCoverage_recursiveReporter,
  MockedTest_default,
  MockedTest_multipleResultState,
  MockedTest_warnOnSlowerThan,
  MockedTestResult_default,
  MockedTestResult_showSlowest,
  MockedTestResult_showWarnings,
  MockedTestResult_showWarningsWithOther,
  MockedTestResult_warnOnSlowerThan,
} from './mocks';
import CustomReporter from './reporter';

let reporter = null;
let expectedFinalTable: unknown[][] = [];

beforeEach(() => {
  console.log = jest.fn();
  console.warn = jest.fn();
  console.error = jest.fn();

  reporter = null;
  expectedFinalTable = [TABLE_HEADER_WITH_WARNINGS_DATA];
});

describe('@90poe/jest-performant-warnings-stats-reporter', () => {
  it('should work with No config provided, default set up', () => {
    const globalC = {};
    const customC = {};
    const expectedTotalNumberOfTestSuites =
      MockedAggregatedResultWithoutCoverage.numTotalTestSuites;
    const expectedOnRunStartConsoleLog = cyan(
      `\nRunning ${expectedTotalNumberOfTestSuites} test suites, quietly for speed 🏎️ `,
    );
    const expectedTableRow = [
      MockedTestResult_default.testFilePath.split('/').slice(-1),
      '0',
      `${MockedTestResult_default.perfStats.runtime / 1000}s`,
    ];

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_default);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expectedFinalTable.push(expectedTableRow);

    expect(console.log).toHaveBeenCalledWith(expectedOnRunStartConsoleLog);
    expect(console.log).toHaveBeenCalledWith(table(expectedFinalTable));
    expect(console.log).toBeCalledTimes(2);
  });

  it('should colour duration values in red when test duration is greater than "warnOnSlowerThan"', () => {
    const globalC = {};
    const customC = {
      warnOnSlowerThan: 700,
    };
    const expectedTableRow = [
      MockedTestResult_warnOnSlowerThan.testFilePath.split('/').slice(-1),
      '0',
      `${red(MockedTestResult_warnOnSlowerThan.perfStats.runtime / 1000)}s`,
    ];

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(
      MockedTest_warnOnSlowerThan,
      MockedTestResult_warnOnSlowerThan,
    );
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expectedFinalTable.push(expectedTableRow);

    expect(console.log).toHaveBeenCalledWith(table(expectedFinalTable));
    expect(console.log).toBeCalledTimes(2);
  });

  it('should show slowest tests when "showSlowest" flag is "true"', () => {
    const globalC = {};
    const customC = {
      showSlowest: true,
    };
    const expectedSlowestTestsOutput = {
      totalInfo: 'Top 5 slowest examples (3.5 seconds, 100.0% of total time):',
      firstSlowTestTitle: '  testing test suite - 2',
      firstSlowTestDuration: '    0.9 seconds /src/test_showSlowest.test.ts',
      secondSlowTestTitle: '  testing test suite - 1',
      secondSlowTestDuration: '    0.8 seconds /src/test_showSlowest.test.ts',
      thirdSlowTestTitle: '  testing test suite - 3',
      thirdSlowTestDuration: '    0.7 seconds /src/test_showSlowest.test.ts',
      fourthSlowTestTitle: '  testing test suite - 5',
      fourthSlowTestDuration: '    0.6 seconds /src/test_showSlowest.test.ts',
      fifthSlowTestTitle: '  testing test suite - 4',
      fifthSlowTestDuration: '    0.5 seconds /src/test_showSlowest.test.ts',
    };

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_showSlowest);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.totalInfo,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.firstSlowTestTitle,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.firstSlowTestDuration,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.secondSlowTestTitle,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.secondSlowTestDuration,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.thirdSlowTestTitle,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.thirdSlowTestDuration,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.fourthSlowTestTitle,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.fourthSlowTestDuration,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.fifthSlowTestTitle,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.fifthSlowTestDuration,
    );
    expect(console.log).toBeCalledTimes(13);
  });

  it('should show slowest tests when "showSlowest" flag is "true" & "maxSlowTests" is provided', () => {
    const globalC = {};
    const customC = {
      showSlowest: true,
      maxSlowTests: 2,
    };
    const expectedSlowestTestsOutput = {
      totalInfo: 'Top 2 slowest examples (1.7 seconds, 48.6% of total time):',
      firstSlowTestTitle: '  testing test suite - 1',
      firstSlowTestDuration: '    0.9 seconds /src/test_showSlowest.test.ts',
      secondSlowTestTitle: '  testing test suite - 2',
      secondSlowTestDuration: '    0.8 seconds /src/test_showSlowest.test.ts',
    };

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_showSlowest);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.totalInfo,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.firstSlowTestTitle,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.firstSlowTestDuration,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.secondSlowTestTitle,
    );
    expect(console.log).toHaveBeenCalledWith(
      expectedSlowestTestsOutput.secondSlowTestDuration,
    );
    expect(console.log).toBeCalledTimes(7);
  });

  it('should Not show slowest tests when "showSlowest" flag is "false" & "maxSlowTests" is provided', () => {
    const globalC = {};
    const customC = {
      maxSlowTests: 2,
    };
    const expectedSlowestTestsOutput = {
      totalInfo: 'Top 2 slowest examples (1.7 seconds, 48.6% of total time):',
      firstSlowTestTitle: '  testing test suite - 1',
      firstSlowTestDuration: '    0.9 seconds /src/test_showSlowest.test.ts',
    };

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_showSlowest);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expect(console.log).not.toHaveBeenCalledWith(
      expectedSlowestTestsOutput.totalInfo,
    );

    expect(console.log).not.toHaveBeenCalledWith(
      expectedSlowestTestsOutput.firstSlowTestTitle,
    );
    expect(console.log).not.toHaveBeenCalledWith(
      expectedSlowestTestsOutput.firstSlowTestDuration,
    );
    expect(console.log).toBeCalledTimes(2);
  });

  it('should show warnings details in finalTable if "showWarningSummary" flag is "true" & "warningsConfig" is provided', () => {
    const globalC = {};
    const customC = {
      showWarningSummary: true,
      warningsConfig: [
        {
          name: 'Warning on act(...)',
          regex: /act(...)/,
        },
        {
          name: 'Warning on Apollo',
          regex: /Apollo/,
        },
        {
          name: 'Warning on Unhandled',
          regex: /Unhandled/,
        },
      ],
    };
    const expectedWarningsOutput = {
      actWarnings: {
        title: ' > Warning on act(...)',
        count: 3,
      },
      apolloWarnings: {
        title: ' > Warning on Apollo',
        count: 1,
      },
      unhandledWarning: {
        title: ' > Warning on Unhandled',
        count: 1,
      },
      other: {
        title: ' > Other',
        count: 3,
      },
    };
    const expectedTableRow = [
      MockedTestResult_showWarnings.testFilePath.split('/').slice(-1),
      `${MockedTestResult_showWarnings.console.length}`,
      `${MockedTestResult_showWarnings.perfStats.runtime / 1000}s`,
    ];

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_showWarnings);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expectedFinalTable.push(expectedTableRow);

    Object.values(expectedWarningsOutput).forEach(({ title, count }) =>
      expectedFinalTable.push([title, count, '']),
    );

    const tableWithWarningsInfo = table(expectedFinalTable);

    expect(console.log).toHaveBeenCalledWith(tableWithWarningsInfo);
    expect(console.log).toBeCalledTimes(2);
  });

  it('should Not show warnings details in finalTable if "showWarningSummary" flag is "false" & "warningsConfig" is provided', () => {
    const globalC = {};
    const customC = {
      warningsConfig: [
        {
          name: 'Warning on act(...)',
          regex: /act(...)/,
        },
        {
          name: 'Warning on Apollo',
          regex: /Apollo/,
        },
        {
          name: 'Warning on Unhandled',
          regex: /Unhandled/,
        },
      ],
    };
    const expectedTableRow = [
      MockedTestResult_showWarnings.testFilePath.split('/').slice(-1),
      `${MockedTestResult_showWarnings.console.length}`,
      `${MockedTestResult_showWarnings.perfStats.runtime / 1000}s`,
    ];

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_showWarnings);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expectedFinalTable.push(expectedTableRow);

    const tableWithoutWarningsInfo = table(expectedFinalTable);

    expect(console.log).toHaveBeenCalledWith(tableWithoutWarningsInfo);
    expect(console.log).toBeCalledTimes(2);
  });

  it('should contain "Other" as a table row with number of warnings that do Not match any from the "warningConfig"', () => {
    const globalC = {};
    const customC = {
      showWarningSummary: true,
      warningsConfig: [
        {
          name: 'Warning on act(...)',
          regex: /act(...)/,
        },
        {
          name: 'Warning on Apollo',
          regex: /Apollo/,
        },
        {
          name: 'Warning on Unhandled',
          regex: /Unhandled/,
        },
      ],
    };
    const expectedWarningsOutput = {
      actWarnings: {
        title: ' > Warning on act(...)',
        count: 3,
      },
      apolloWarnings: {
        title: ' > Warning on Apollo',
        count: 2,
      },
      unhandledWarning: {
        title: ' > Warning on Unhandled',
        count: 1,
      },
      other: {
        title: ' > Other',
        count: 2,
      },
    };
    const expectedTableRow = [
      MockedTestResult_showWarningsWithOther.testFilePath.split('/').slice(-1),
      `${MockedTestResult_showWarningsWithOther.console.length}`,
      `${MockedTestResult_showWarningsWithOther.perfStats.runtime / 1000}s`,
    ];

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(
      MockedTest_default,
      MockedTestResult_showWarningsWithOther,
    );
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expectedFinalTable.push(expectedTableRow);

    Object.values(expectedWarningsOutput).forEach(({ title, count }) =>
      expectedFinalTable.push([title, count, '']),
    );

    const tableWithWarningsInfo = table(expectedFinalTable);

    expect(console.log).toHaveBeenCalledWith(tableWithWarningsInfo);
    expect(console.log).toBeCalledTimes(2);
  });

  it('should log numPassedTests, numFailedTests, FailureMessage', () => {
    const globalC = {};
    const customC = {};
    const expectedFailureMessage =
      MockedAggregatedResultWithoutCoverage_multipleResultState.testResults[0]
        .failureMessage;
    const { numPassedTests: expectedNumPassedTests } =
      MockedAggregatedResultWithoutCoverage_multipleResultState;
    const { numFailedTests: expectedNumFailedTests } =
      MockedAggregatedResultWithoutCoverage_multipleResultState;

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(
      MockedAggregatedResultWithoutCoverage_multipleResultState,
    );
    reporter.onTestResult(
      MockedTest_multipleResultState,
      MockedTestResult_default,
    );
    reporter.onRunComplete(
      new Set(),
      MockedAggregatedResultWithoutCoverage_multipleResultState,
    );

    expect(console.log).toHaveBeenCalledWith(
      green(`${expectedNumPassedTests} passed`),
    );
    expect(console.log).toHaveBeenCalledWith(`${expectedFailureMessage}`);
    expect(console.log).toHaveBeenCalledWith(
      bold(red(`${expectedNumFailedTests} failed`)),
    );
    expect(console.log).toBeCalledTimes(8);
  });

  it('should console warnings, errors in case config has "verbose"', () => {
    const globalC = {
      verbose: true,
    };
    const customC = {};
    const fileName = bold('test_showWarnings');
    const warningMessage = yellow(
      'Warning Message - act(...) for MockedTestResult_showWarnings',
    );
    const errorMessage = red('Error message');

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_showWarnings);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expect(console.log).toBeCalledWith(fileName);

    expect(console.warn).toBeCalledWith(warningMessage);
    expect(console.error).toBeCalledWith(errorMessage);

    expect(console.log).toBeCalledTimes(5);
    expect(console.warn).toBeCalledTimes(10);
    expect(console.error).toBeCalledTimes(2);
  });

  it('should not console final table in case number of watching tests is 0', () => {
    const globalC = {
      watch: true,
    };
    const customC = {};

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expect(console.log).not.toBeCalledWith(expectedFinalTable);
  });

  it('should console final table without warnings data in case config has "verbose"', () => {
    const globalC = {
      verbose: true,
    };
    const customC = {
      showWarningSummary: true,
    };
    const expectedTableRow = [
      MockedTestResult_showWarnings.testFilePath.split('/').slice(-1),
      `${MockedTestResult_showWarnings.perfStats.runtime / 1000}s`,
    ];

    expectedFinalTable = [TABLE_HEADER_WITHOUT_WARNINGS_DATA];

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_showWarnings);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expectedFinalTable.push(expectedTableRow);

    const tableWithoutWarningsInfo = table(expectedFinalTable);

    expect(console.log).toBeCalledWith(tableWithoutWarningsInfo);
  });

  it('should be quiet in case config has "verbose" and there are no warnings', () => {
    const globalC = {
      verbose: true,
    };
    const customC = {};
    const anyWarningMessage = yellow(
      'Warning Message - act(...) for MockedTestResult_showWarnings',
    );

    reporter = new CustomReporter(globalC, customC);
    reporter.onRunStart(MockedAggregatedResultWithoutCoverage);
    reporter.onTestResult(MockedTest_default, MockedTestResult_default);
    reporter.onRunComplete(new Set(), MockedAggregatedResultWithoutCoverage);

    expect(console.warn).not.toBeCalledWith(anyWarningMessage);
  });
});

it('should work outside of describe block', () => {
  const globalC = {};
  const customC = {};

  reporter = new CustomReporter(globalC, customC);
  reporter.onRunStart(MockedAggregatedResultWithoutCoverage_recursiveReporter);
  reporter.onTestResult(MockedTest_default, MockedTestResult_default);
  reporter.onRunComplete(
    new Set(),
    MockedAggregatedResultWithoutCoverage_recursiveReporter,
  );

  expect(console.log).toHaveBeenCalledWith(
    green('PASSED - Title in recursiveReporter'),
  );
  expect(console.log).toBeCalledTimes(3);
});
