import { QueryBuilderOperation, QueryBuilderOperationDef } from '../../prometheus/querybuilder/shared/types';

import {
  createRangeOperation,
  createRangeOperationWithGrouping,
  getLineFilterRenderer,
  isConflictingFilter,
  labelFilterRenderer,
  pipelineRenderer,
} from './operationUtils';
import { getOperationDefinitions } from './operations';
import { LokiOperationId, LokiVisualQueryOperationCategory } from './types';

describe('createRangeOperation', () => {
  it('should create basic range operation without possible grouping', () => {
    expect(createRangeOperation('test_range_operation')).toMatchObject({
      id: 'test_range_operation',
      name: 'Test range operation',
      params: [{ name: 'Range', type: 'string' }],
      defaultParams: ['$__auto'],
      alternativesKey: 'range function',
      category: LokiVisualQueryOperationCategory.RangeFunctions,
    });
  });

  it('should create basic range operation with possible grouping', () => {
    expect(createRangeOperation('test_range_operation', true)).toMatchObject({
      id: 'test_range_operation',
      name: 'Test range operation',
      params: [
        { name: 'Range', type: 'string' },
        {
          name: 'By label',
          type: 'string',
          restParam: true,
          optional: true,
        },
      ],
      defaultParams: ['$__auto'],
      alternativesKey: 'range function',
      category: LokiVisualQueryOperationCategory.RangeFunctions,
    });
  });

  it('should create range operation for quantile_over_time', () => {
    expect(createRangeOperation('quantile_over_time', true)).toMatchObject({
      id: 'quantile_over_time',
      name: 'Quantile over time',
      params: [
        { name: 'Range', type: 'string' },
        { name: 'Quantile', type: 'number' },
        { name: 'By label', type: 'string', restParam: true, optional: true },
      ],
      defaultParams: ['$__auto', '0.95'],
      alternativesKey: 'range function',
      category: LokiVisualQueryOperationCategory.RangeFunctions,
    });
  });
});

describe('createRangeOperationWithGrouping', () => {
  it('returns correct operation definitions with overrides and params', () => {
    const operations = createRangeOperationWithGrouping('quantile_over_time');
    expect(operations).toHaveLength(3);
    expect(operations[0]).toMatchObject({
      id: 'quantile_over_time',
      name: 'Quantile over time',
      params: [
        { name: 'Range', type: 'string' },
        { name: 'Quantile', type: 'number' },
        { name: 'By label', type: 'string', restParam: true, optional: true },
      ],
      defaultParams: ['$__auto', '0.95'],
      alternativesKey: 'range function',
      category: LokiVisualQueryOperationCategory.RangeFunctions,
    });

    expect(operations[1]).toMatchObject({
      id: '__quantile_over_time_by',
      name: 'Quantile over time by',
      params: [
        { name: 'Range', type: 'string' },
        { name: 'Quantile', type: 'number' },
        { name: 'Label', type: 'string', restParam: true, optional: true },
      ],
      defaultParams: ['$__auto', '0.95', ''],
      alternativesKey: 'range function with grouping',
      category: LokiVisualQueryOperationCategory.RangeFunctions,
    });

    expect(operations[2]).toMatchObject({
      id: '__quantile_over_time_without',
      name: 'Quantile over time without',
      params: [
        { name: 'Range', type: 'string' },
        { name: 'Quantile', type: 'number' },
        { name: 'Label', type: 'string', restParam: true, optional: true },
      ],
      defaultParams: ['$__auto', '0.95', ''],
      alternativesKey: 'range function with grouping',
      category: LokiVisualQueryOperationCategory.RangeFunctions,
    });
  });

  it('returns correct query string using range operation definitions for quantile_over_time with by grouping', () => {
    const operations = createRangeOperationWithGrouping('quantile_over_time');
    const query = operations[1].renderer(
      { id: '__quantile_over_time_by', params: ['[5m]', '0.95', 'source', 'place'] },
      operations[1],
      '{job="grafana"}'
    );
    expect(query).toBe('quantile_over_time(0.95, {job="grafana"} [[5m]]) by (source, place)');
  });

  it('returns correct query string using range operation definitions for quantile_over_time with without grouping', () => {
    const operations = createRangeOperationWithGrouping('quantile_over_time');
    const query = operations[2].renderer(
      { id: '__quantile_over_time_without', params: ['[$__interval]', '0.91', 'source', 'place'] },
      operations[2],
      '{job="grafana"}'
    );
    expect(query).toBe('quantile_over_time(0.91, {job="grafana"} [[$__interval]]) without (source, place)');
  });

  it('returns correct query string using range operation definitions for avg_over_time with without grouping', () => {
    const operations = createRangeOperationWithGrouping('avg_over_time');
    const query = operations[2].renderer(
      { id: '__avg_over_time_without', params: ['[$__interval]', 'source'] },
      operations[2],
      '{job="grafana"}'
    );
    expect(query).toBe('avg_over_time({job="grafana"} [[$__interval]]) without (source)');
  });
});

describe('getLineFilterRenderer', () => {
  const MOCK_MODEL = {
    id: '__line_contains',
    params: ['error'],
  };
  const MOCK_MODEL_INSENSITIVE = {
    id: '__line_contains_case_insensitive',
    params: ['ERrOR'],
  };

  const MOCK_DEF = undefined as unknown as QueryBuilderOperationDef;

  const MOCK_INNER_EXPR = '{job="grafana"}';

  it('getLineFilterRenderer returns a function', () => {
    const lineFilterRenderer = getLineFilterRenderer('!~');
    expect(typeof lineFilterRenderer).toBe('function');
  });

  it('lineFilterRenderer returns the correct query for line contains', () => {
    const lineFilterRenderer = getLineFilterRenderer('!~');
    expect(lineFilterRenderer(MOCK_MODEL, MOCK_DEF, MOCK_INNER_EXPR)).toBe('{job="grafana"} !~ `error`');
  });

  it('lineFilterRenderer returns the correct query for line contains case insensitive', () => {
    const lineFilterRenderer = getLineFilterRenderer('!~', true);
    expect(lineFilterRenderer(MOCK_MODEL_INSENSITIVE, MOCK_DEF, MOCK_INNER_EXPR)).toBe(
      '{job="grafana"} !~ `(?i)ERrOR`'
    );
  });
});

describe('labelFilterRenderer', () => {
  const MOCK_MODEL = { id: '__label_filter', params: ['label', '', 'value'] };
  const MOCK_DEF = undefined as unknown as QueryBuilderOperationDef;
  const MOCK_INNER_EXPR = '{job="grafana"}';

  it.each`
    operator | type        | expected
    ${'='}   | ${'string'} | ${'`value`'}
    ${'!='}  | ${'string'} | ${'`value`'}
    ${'=~'}  | ${'string'} | ${'`value`'}
    ${'!~'}  | ${'string'} | ${'`value`'}
    ${'>'}   | ${'number'} | ${'value'}
    ${'>='}  | ${'number'} | ${'value'}
    ${'<'}   | ${'number'} | ${'value'}
    ${'<='}  | ${'number'} | ${'value'}
  `("value should be of type '$type' when operator is: $operator", ({ operator, expected }) => {
    MOCK_MODEL.params[1] = operator;
    expect(labelFilterRenderer(MOCK_MODEL, MOCK_DEF, MOCK_INNER_EXPR)).toBe(
      `{job="grafana"} | label ${operator} ${expected}`
    );
  });
});

describe('isConflictingFilter', () => {
  it('should return true if the operation conflict with another label filter', () => {
    const operation = { id: '__label_filter', params: ['abc', '!=', '123'] };
    const queryOperations = [
      { id: '__label_filter', params: ['abc', '=', '123'] },
      { id: '__label_filter', params: ['abc', '!=', '123'] },
    ];
    expect(isConflictingFilter(operation, queryOperations)).toBe(true);
  });

  it("should return false if the operation doesn't conflict with another label filter", () => {
    const operation = { id: '__label_filter', params: ['abc', '=', '123'] };
    const queryOperations = [
      { id: '__label_filter', params: ['abc', '=', '123'] },
      { id: '__label_filter', params: ['abc', '=', '123'] },
    ];
    expect(isConflictingFilter(operation, queryOperations)).toBe(false);
  });
});

describe('pipelineRenderer', () => {
  let definitions: QueryBuilderOperationDef[];
  beforeEach(() => {
    definitions = getOperationDefinitions();
  });

  it('Correctly renders unpack expressions', () => {
    const model: QueryBuilderOperation = {
      id: LokiOperationId.Unpack,
      params: [],
    };
    const definition = definitions.find((def) => def.id === LokiOperationId.Unpack);
    expect(pipelineRenderer(model, definition!, '{}')).toBe('{} | unpack');
  });
});
