import {LegendComponentOption} from 'echarts';

import {Series} from 'sentry/types/echarts';
import {
  axisLabelFormatter,
  categorizeDuration,
  findRangeOfMultiSeries,
  getDurationUnit,
  tooltipFormatter,
} from 'sentry/utils/discover/charts';
import {HOUR, MINUTE, SECOND} from 'sentry/utils/formatters';

describe('tooltipFormatter()', () => {
  it('formats values', () => {
    const cases: [string, number, string][] = [
      // function, input, expected
      ['count()', 0.1, '0.1'],
      ['avg(thing)', 0.125126, '0.125'],
      ['failure_rate()', 0.66123, '66.12%'],
      ['p50()', 100, '100.00ms'],
      ['p50()', 100.23, '100.23ms'],
      ['p50()', 1200, '1.20s'],
      ['p50()', 86400000, '1.00d'],
    ];
    for (const scenario of cases) {
      expect(tooltipFormatter(scenario[1], scenario[0])).toEqual(scenario[2]);
    }
  });
});

describe('axisLabelFormatter()', () => {
  it('formats values', () => {
    const cases: [string, number, string][] = [
      // type, input, expected
      ['count()', 0.1, '0.1'],
      ['avg(thing)', 0.125126, '0.125'],
      ['failure_rate()', 0.66123, '66%'],
      ['p50()', 100, '100ms'],
      ['p50()', 541, '541ms'],
      ['p50()', 1200, '1s'],
      ['p50()', 60000, '1min'],
      ['p50()', 120000, '2min'],
      ['p50()', 3600000, '1hr'],
      ['p50()', 86400000, '1d'],
    ];
    for (const scenario of cases) {
      expect(axisLabelFormatter(scenario[1], scenario[0])).toEqual(scenario[2]);
    }
  });

  describe('When a duration unit is passed', () => {
    const getAxisLabels = (axisValues: number[], durationUnit: number) => {
      return axisValues.map(value =>
        axisLabelFormatter(value, 'p50()', undefined, durationUnit)
      );
    };

    const generateDurationUnit = (axisValues: number[]) => {
      const max = Math.max(...axisValues);
      const min = Math.min(...axisValues);
      return categorizeDuration((max + min) * 0.5);
    };

    it('should not contain duplicate axis labels', () => {
      const axisValues = [40 * SECOND, 50 * SECOND, 60 * SECOND, 70 * SECOND];
      const durationUnit = generateDurationUnit(axisValues);
      const labels = getAxisLabels(axisValues, durationUnit);
      expect(labels.length).toBe(new Set(labels).size);
    });

    it('should use the same duration unit', () => {
      const axisValues = [50 * MINUTE, 150 * MINUTE, 250 * MINUTE, 350 * MINUTE];
      const durationUnit = generateDurationUnit(axisValues);
      const labels = getAxisLabels(axisValues, durationUnit);
      expect(labels.length).toBe(labels.filter(label => label.endsWith('hr')).length);
    });
  });
});

describe('findRangeOfMultiSeries()', () => {
  const series: Series[] = [
    {
      seriesName: 'p100()',
      data: [
        {name: 1, value: 2300},
        {name: 2, value: 1900},
        {name: 3, value: 1950},
      ],
    },
    {
      seriesName: 'p95()',
      data: [
        {name: 1, value: 300},
        {name: 2, value: 280},
        {name: 3, value: 290},
      ],
    },
    {
      seriesName: 'p50()',
      data: [
        {name: 1, value: 100},
        {name: 2, value: 50},
        {name: 3, value: 80},
      ],
    },
  ];

  it('should find min and max when no items selected in legend', () => {
    expect(findRangeOfMultiSeries(series)).toStrictEqual({max: 2300, min: 50});
  });

  it('should ignore p100 series if not selected', () => {
    const legend: LegendComponentOption = {
      selected: {'p100()': false},
    };
    expect(findRangeOfMultiSeries(series, legend)).toStrictEqual({max: 300, min: 50});
  });

  it('should ignore p50 series if not selected', () => {
    const legend: LegendComponentOption = {
      selected: {'p50()': false},
    };
    expect(findRangeOfMultiSeries(series, legend)).toStrictEqual({max: 2300, min: 280});
  });

  it('should display p100 value if selected and in legend object', () => {
    const legend: LegendComponentOption = {
      selected: {'p100()': true},
    };
    expect(findRangeOfMultiSeries(series, legend)).toStrictEqual({max: 2300, min: 50});
  });
});

describe('getDurationUnit()', () => {
  const MILLISECOND = 1;
  const generateSeries = (axisValues: number[]): Series[] => {
    return [
      {
        seriesName: 'p100()',
        data: axisValues.map((val, idx) => ({name: idx, value: val})),
      },
    ];
  };

  it('should return ms during transtion between ms to s', () => {
    const series = generateSeries([700, 800, 900, SECOND, 1.1 * SECOND]);

    expect(getDurationUnit(series)).toBe(MILLISECOND);
  });

  it('should return s during transtion between s to min', () => {
    const series = generateSeries([40 * SECOND, 50 * SECOND, MINUTE, 1.3 * MINUTE]);

    expect(getDurationUnit(series)).toBe(SECOND);
  });

  it('should return ms if y range is small', () => {
    const series = generateSeries([1000, 1050, 1100, 1150, 1200]);

    expect(getDurationUnit(series)).toBe(MILLISECOND);
  });

  it('should return min if yAxis range >= 5 min', () => {
    const series = generateSeries([1 * MINUTE, 2 * MINUTE, 4 * MINUTE, 6 * MINUTE]);

    expect(getDurationUnit(series)).toBe(MINUTE);
  });

  it('should return sec if yAxis range < 5 min', () => {
    const series = generateSeries([1 * MINUTE, 2 * MINUTE, 4 * MINUTE, 5 * MINUTE]);

    expect(getDurationUnit(series)).toBe(SECOND);
  });

  it('should use second with ms yAxis range if label length is long', () => {
    const series = generateSeries([4 * HOUR, 4.0001 * HOUR, 4.0002 * HOUR]);
    const durationUnit = getDurationUnit(series);
    const numOfDigits = ((4.0001 * HOUR) / durationUnit).toFixed(0).length;
    expect(numOfDigits).toBeLessThan(6);
    expect(durationUnit).not.toBe(MILLISECOND);
  });
});
