import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen, waitFor } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { DataSourceInstanceSettings } from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { MetricsQueryEditor, normalizeQuery, Props } from './MetricsQueryEditor';
import { CloudWatchDatasource } from '../datasource';
import { CustomVariableModel, initialVariableModelState } from '../../../../features/variables/types';
import { CloudWatchJsonData, CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType } from '../types';

const setup = () => {
  const instanceSettings = {
    jsonData: { defaultRegion: 'us-east-1' },
  } as DataSourceInstanceSettings<CloudWatchJsonData>;

  const templateSrv = new TemplateSrv();
  const variable: CustomVariableModel = {
    ...initialVariableModelState,
    id: 'var3',
    index: 0,
    name: 'var3',
    options: [
      { selected: true, value: 'var3-foo', text: 'var3-foo' },
      { selected: false, value: 'var3-bar', text: 'var3-bar' },
      { selected: true, value: 'var3-baz', text: 'var3-baz' },
    ],
    current: { selected: true, value: ['var3-foo', 'var3-baz'], text: 'var3-foo + var3-baz' },
    multi: true,
    includeAll: false,
    query: '',
    type: 'custom',
  };
  templateSrv.init([variable]);

  const datasource = new CloudWatchDatasource(instanceSettings, templateSrv as any, {} as any);
  datasource.metricFindQuery = async () => [{ value: 'test', label: 'test', text: 'test' }];
  datasource.getNamespaces = jest.fn().mockResolvedValue([]);
  datasource.getMetrics = jest.fn().mockResolvedValue([]);
  datasource.getRegions = jest.fn().mockResolvedValue([]);
  datasource.getDimensionKeys = jest.fn().mockResolvedValue([]);

  const props: Props = {
    query: {
      queryMode: 'Metrics',
      refId: '',
      id: '',
      region: 'us-east-1',
      namespace: 'ec2',
      metricName: 'CPUUtilization',
      dimensions: { somekey: 'somevalue' },
      statistic: '',
      period: '',
      expression: '',
      alias: '',
      matchExact: true,
      metricQueryType: MetricQueryType.Search,
      metricEditorMode: MetricEditorMode.Builder,
    },
    datasource,
    history: [],
    onChange: jest.fn(),
    onRunQuery: jest.fn(),
  };

  return props;
};

describe('QueryEditor', () => {
  it('should render component', async () => {
    const { act } = renderer;
    await act(async () => {
      const props = setup();
      const tree = renderer.create(<MetricsQueryEditor {...props} />).toJSON();
      expect(tree).toMatchSnapshot();
    });
  });

  it('normalizes query on mount', async () => {
    const { act } = renderer;
    const props = setup();
    // This does not actually even conform to the prop type but this happens on initialisation somehow
    props.query = {
      queryMode: 'Metrics',
      apiMode: 'Metrics',
      refId: '',
      expression: '',
      matchExact: true,
      metricQueryType: MetricQueryType.Search,
      metricEditorMode: MetricEditorMode.Builder,
    } as any;
    await act(async () => {
      renderer.create(<MetricsQueryEditor {...props} />);
    });
    expect((props.onChange as jest.Mock).mock.calls[0][0]).toEqual({
      namespace: '',
      metricName: '',
      expression: '',
      sqlExpression: '',
      dimensions: {},
      region: 'default',
      id: '',
      alias: '',
      statistic: 'Average',
      period: '',
      queryMode: 'Metrics',
      apiMode: 'Metrics',
      refId: '',
      matchExact: true,
      metricQueryType: MetricQueryType.Search,
      metricEditorMode: MetricEditorMode.Builder,
    });
  });

  describe('should use correct default values', () => {
    it('should normalize query with default values', () => {
      expect(normalizeQuery({ refId: '42' } as any)).toEqual({
        namespace: '',
        metricName: '',
        expression: '',
        sqlExpression: '',
        dimensions: {},
        region: 'default',
        id: '',
        alias: '',
        statistic: 'Average',
        matchExact: true,
        period: '',
        queryMode: 'Metrics',
        refId: '42',
        metricQueryType: MetricQueryType.Search,
        metricEditorMode: MetricEditorMode.Builder,
      });
    });
  });

  describe('should handle editor modes correctly', () => {
    it('when metric query type is metric search and editor mode is builder', async () => {
      await act(async () => {
        const props = setup();
        render(<MetricsQueryEditor {...props} />);

        expect(screen.getByText('Metric Search')).toBeInTheDocument();
        const radio = screen.getByLabelText('Builder');
        expect(radio instanceof HTMLInputElement && radio.checked).toBeTruthy();
      });
    });

    it('when metric query type is metric search and editor mode is raw', async () => {
      await act(async () => {
        const props = setup();
        (props.query as CloudWatchMetricsQuery).metricEditorMode = MetricEditorMode.Code;
        render(<MetricsQueryEditor {...props} />);

        expect(screen.getByText('Metric Search')).toBeInTheDocument();
        const radio = screen.getByLabelText('Code');
        expect(radio instanceof HTMLInputElement && radio.checked).toBeTruthy();
      });
    });

    it('when metric query type is metric query and editor mode is builder', async () => {
      await act(async () => {
        const props = setup();
        (props.query as CloudWatchMetricsQuery).metricQueryType = MetricQueryType.Query;
        (props.query as CloudWatchMetricsQuery).metricEditorMode = MetricEditorMode.Builder;
        render(<MetricsQueryEditor {...props} />);

        expect(screen.getByText('Metric Query')).toBeInTheDocument();
        const radio = screen.getByLabelText('Builder');
        expect(radio instanceof HTMLInputElement && radio.checked).toBeTruthy();
      });
    });

    it('when metric query type is metric query and editor mode is raw', async () => {
      await act(async () => {
        const props = setup();
        (props.query as CloudWatchMetricsQuery).metricQueryType = MetricQueryType.Query;
        (props.query as CloudWatchMetricsQuery).metricEditorMode = MetricEditorMode.Code;
        render(<MetricsQueryEditor {...props} />);

        expect(screen.getByText('Metric Query')).toBeInTheDocument();
        const radio = screen.getByLabelText('Code');
        expect(radio instanceof HTMLInputElement && radio.checked).toBeTruthy();
      });
    });
  });

  describe('should handle expression options correctly', () => {
    it('should display match exact switch', () => {
      const props = setup();
      render(<MetricsQueryEditor {...props} />);
      expect(screen.getByText('Match exact')).toBeInTheDocument();
    });

    it('shoud display wildcard option in dimension value dropdown', async () => {
      const props = setup();
      props.datasource.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]);
      (props.query as CloudWatchMetricsQuery).metricQueryType = MetricQueryType.Search;
      (props.query as CloudWatchMetricsQuery).metricEditorMode = MetricEditorMode.Builder;
      (props.query as CloudWatchMetricsQuery).dimensions = { instanceId: 'instance-123' };
      render(<MetricsQueryEditor {...props} />);
      expect(screen.getByText('Match exact')).toBeInTheDocument();

      const valueElement = screen.getByText('instance-123');
      expect(valueElement).toBeInTheDocument();
      expect(screen.queryByText('*')).toBeNull();
      act(async () => {
        await valueElement.click();
        await waitFor(() => {
          expect(screen.getByText('*')).toBeInTheDocument();
        });
      });
    });
  });
});
