import { toDataQueryError, PanelQueryState, getProcessedDataFrame } from './PanelQueryState';
import { MockDataSourceApi } from 'test/mocks/datasource_srv';
import { LoadingState } from '@grafana/data';
import { DataQueryResponse } from '@grafana/ui';
import { getQueryOptions } from 'test/helpers/getQueryOptions';

describe('PanelQueryState', () => {
  it('converts anythign to an error', () => {
    let err = toDataQueryError(undefined);
    expect(err.message).toEqual('Query error');

    err = toDataQueryError('STRING ERRROR');
    expect(err.message).toEqual('STRING ERRROR');

    err = toDataQueryError({ message: 'hello' });
    expect(err.message).toEqual('hello');
  });

  it('keeps track of running queries', async () => {
    const state = new PanelQueryState();
    expect(state.getActiveRunner()).toBeFalsy();
    let hasRun = false;
    const dsRunner = new Promise<DataQueryResponse>((resolve, reject) => {
      // The status should be running when we get here
      expect(state.getActiveRunner()).toBeTruthy();
      resolve({ data: ['x', 'y'] });
      hasRun = true;
    });
    const ds = new MockDataSourceApi('test');
    ds.queryResolver = dsRunner;

    // should not actually run for an empty query
    let empty = await state.execute(ds, getQueryOptions({}));
    expect(state.getActiveRunner()).toBeFalsy();
    expect(empty.series.length).toBe(0);
    expect(hasRun).toBeFalsy();

    const query = getQueryOptions({
      targets: [{ hide: true, refId: 'X' }, { hide: true, refId: 'Y' }, { hide: true, refId: 'Z' }],
    });

    empty = await state.execute(ds, query);
    // should not run any hidden queries'
    expect(state.getActiveRunner()).toBeFalsy();
    expect(empty.series.length).toBe(0);
    expect(hasRun).toBeFalsy();

    // Check for the same query
    expect(state.isSameQuery(ds, query)).toBeTruthy();

    // Check for differnet queries
    expect(state.isSameQuery(new MockDataSourceApi('test'), query)).toBeFalsy();
    expect(state.isSameQuery(ds, getQueryOptions({ targets: [{ refId: 'differnet' }] }))).toBeFalsy();
  });
});

describe('getProcessedDataFrame', () => {
  it('converts timeseries to table skipping nulls', () => {
    const input1 = {
      target: 'Field Name',
      datapoints: [[100, 1], [200, 2]],
    };
    const input2 = {
      // without target
      target: '',
      datapoints: [[100, 1], [200, 2]],
    };
    const data = getProcessedDataFrame([null, input1, input2, null, null]);
    expect(data.length).toBe(2);
    expect(data[0].fields[0].name).toBe(input1.target);
    expect(data[0].rows).toBe(input1.datapoints);

    // Default name
    expect(data[1].fields[0].name).toEqual('Value');

    // Every colun should have a name and a type
    for (const table of data) {
      for (const column of table.fields) {
        expect(column.name).toBeDefined();
        expect(column.type).toBeDefined();
      }
    }
  });

  it('supports null values from query OK', () => {
    expect(getProcessedDataFrame([null, null, null, null])).toEqual([]);
    expect(getProcessedDataFrame(undefined)).toEqual([]);
    expect(getProcessedDataFrame((null as unknown) as any[])).toEqual([]);
    expect(getProcessedDataFrame([])).toEqual([]);
  });
});

function makeSeriesStub(refId: string) {
  return {
    fields: [{ name: 'a' }],
    rows: [],
    refId,
  };
}

describe('stream handling', () => {
  const state = new PanelQueryState();
  state.onStreamingDataUpdated = () => {
    // nothing
  };
  state.request = {
    requestId: '123',
    range: {
      raw: {
        from: 123, // if string it gets revaluated
      },
    },
  } as any;
  state.response = {
    state: LoadingState.Done,
    series: [makeSeriesStub('A'), makeSeriesStub('B')],
  };

  it('gets the response', () => {
    const data = state.validateStreamsAndGetPanelData();
    expect(data.series.length).toBe(2);
    expect(data.state).toBe(LoadingState.Done);
    expect(data.series[0].refId).toBe('A');
  });

  it('adds a stream event', () => {
    // Post a stream event
    state.dataStreamObserver({
      state: LoadingState.Loading,
      key: 'C',
      request: state.request, // From the same request
      series: [makeSeriesStub('C')],
      unsubscribe: () => {},
    });
    expect(state.streams.length).toBe(1);

    const data = state.validateStreamsAndGetPanelData();
    expect(data.series.length).toBe(3);
    expect(data.state).toBe(LoadingState.Streaming);
    expect(data.series[2].refId).toBe('C');
  });

  it('add another stream event (with a differnet key)', () => {
    // Post a stream event
    state.dataStreamObserver({
      state: LoadingState.Loading,
      key: 'D',
      request: state.request, // From the same request
      series: [makeSeriesStub('D')],
      unsubscribe: () => {},
    });
    expect(state.streams.length).toBe(2);

    const data = state.validateStreamsAndGetPanelData();
    expect(data.series.length).toBe(4);
    expect(data.state).toBe(LoadingState.Streaming);
    expect(data.series[3].refId).toBe('D');
  });

  it('replace the first stream value, but keep the order', () => {
    // Post a stream event
    state.dataStreamObserver({
      state: LoadingState.Loading,
      key: 'C', // The key to replace previous index 2
      request: state.request, // From the same request
      series: [makeSeriesStub('X')],
      unsubscribe: () => {},
    });
    expect(state.streams.length).toBe(2);

    const data = state.validateStreamsAndGetPanelData();
    expect(data.series[2].refId).toBe('X');
  });

  it('ignores streams from a differnet request', () => {
    // Post a stream event
    state.dataStreamObserver({
      state: LoadingState.Loading,
      key: 'Z', // Note with key 'A' it would still overwrite
      request: {
        ...state.request,
        requestId: 'XXX', // Different request and id
      } as any,
      series: [makeSeriesStub('C')],
      unsubscribe: () => {},
    });

    expect(state.streams.length).toBe(2); // no change
    const data = state.validateStreamsAndGetPanelData();
    expect(data.series.length).toBe(4);
  });

  it('removes streams when the query changes', () => {
    state.request = {
      ...state.request,
      requestId: 'somethine else',
    } as any;
    state.response = {
      state: LoadingState.Done,
      series: [makeSeriesStub('F')],
    };
    expect(state.streams.length).toBe(2); // unchanged

    const data = state.validateStreamsAndGetPanelData();
    expect(data.series.length).toBe(1);
    expect(data.series[0].refId).toBe('F');
    expect(state.streams.length).toBe(0); // no streams
  });
});
