import { css } from '@emotion/css';

import { type GrafanaTheme2, type QueryEditorProps, type SelectableValue, toOption } from '@grafana/data';
import { EditorField } from '@grafana/plugin-ui';
import { config } from '@grafana/runtime';
import { TextLink, useStyles2 } from '@grafana/ui';

import { type CloudWatchDatasource } from '../../datasource';
import {
  useAccountOptions,
  useDimensionKeys,
  useMetrics,
  useNamespaces,
  useRegions,
  useEnsureVariableHasSingleSelection,
} from '../../hooks';
import { migrateVariableQuery } from '../../migrations/variableQueryMigrations';
import { type CloudWatchJsonData, type CloudWatchQuery, type VariableQuery, VariableQueryType } from '../../types';
import { ALL_ACCOUNTS_OPTION } from '../shared/Account';
import { Dimensions } from '../shared/Dimensions/Dimensions';

import { MultiFilter } from './MultiFilter';
import { VariableQueryField } from './VariableQueryField';
import { VariableTextField } from './VariableTextField';

export type Props = QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData, VariableQuery>;

const queryTypes: Array<{ value: string; label: string }> = [
  { value: VariableQueryType.Regions, label: 'Regions' },
  { value: VariableQueryType.Namespaces, label: 'Namespaces' },
  { value: VariableQueryType.Metrics, label: 'Metrics' },
  { value: VariableQueryType.DimensionKeys, label: 'Dimension Keys' },
  { value: VariableQueryType.DimensionValues, label: 'Dimension Values' },
  { value: VariableQueryType.EBSVolumeIDs, label: 'EBS Volume IDs' },
  { value: VariableQueryType.EC2InstanceAttributes, label: 'EC2 Instance Attributes' },
  { value: VariableQueryType.ResourceArns, label: 'Resource ARNs' },
  { value: VariableQueryType.Statistics, label: 'Statistics' },
  { value: VariableQueryType.LogGroups, label: 'Log Groups' },
  ...(config.featureToggles.cloudWatchCrossAccountQuerying
    ? [{ value: VariableQueryType.Accounts, label: 'Accounts' }]
    : []),
];

const attributeNames: string[] = [
  'AmiLaunchIndex',
  'Architecture',
  'ClientToken',
  'EbsOptimized',
  'EnaSupport',
  'Hypervisor',
  'IamInstanceProfile',
  'ImageId',
  'InstanceId',
  'InstanceLifecycle',
  'InstanceType',
  'KernelId',
  'KeyName',
  'LaunchTime',
  'Platform',
  'PrivateDnsName',
  'PrivateIpAddress',
  'PublicDnsName',
  'PublicIpAddress',
  'RamdiskId',
  'RootDeviceName',
  'RootDeviceType',
  'SourceDestCheck',
  'SpotInstanceRequestId',
  'SriovNetSupport',
  'SubnetId',
  'VirtualizationType',
  'VpcId',
];

export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
  const parsedQuery = migrateVariableQuery(query);

  const { region, namespace, metricName, dimensionKey } = parsedQuery;
  const [regions, regionIsLoading] = useRegions(datasource);
  const namespaces = useNamespaces(datasource);
  const metrics = useMetrics(datasource, { region, namespace });
  const dimensionKeys = useDimensionKeys(datasource, { region, namespace, metricName });
  const accountState = useAccountOptions(datasource.resources, query.region);
  const dimensionKeyError = useEnsureVariableHasSingleSelection(datasource, dimensionKey);

  const onRegionChange = async (region: string) => {
    const validatedQuery = await sanitizeQuery({
      ...parsedQuery,
      region,
      accountId: undefined,
    });
    onQueryChange(validatedQuery);
  };

  const onNamespaceChange = async (namespace: string) => {
    const validatedQuery = await sanitizeQuery({
      ...parsedQuery,
      namespace,
    });
    onQueryChange(validatedQuery);
  };

  const onQueryChange = (newQuery: VariableQuery) => {
    onChange({
      ...newQuery,
      refId: 'CloudWatchVariableQueryEditor-VariableQuery',
    });
  };

  // Reset dimensionValue parameters if namespace or region change
  const sanitizeQuery = async (query: VariableQuery) => {
    let { metricName, dimensionKey, dimensionFilters, namespace, region } = query;
    if (metricName) {
      await datasource.resources.getMetrics({ namespace, region }).then((result: Array<SelectableValue<string>>) => {
        if (!result.find((metric) => metric.value === metricName)) {
          metricName = '';
        }
      });
    }
    if (dimensionKey) {
      await datasource.resources
        .getDimensionKeys({ namespace, region })
        .then((result: Array<SelectableValue<string>>) => {
          if (!result.find((key) => key.value === dimensionKey)) {
            dimensionKey = '';
            dimensionFilters = {};
          }
        });
    }
    return { ...query, metricName, dimensionKey, dimensionFilters };
  };
  const allAttributeNames = attributeNames.includes(parsedQuery.attributeName)
    ? attributeNames
    : [...attributeNames, parsedQuery.attributeName];
  const attributeOptions = allAttributeNames.map(toOption);

  const hasRegionField = [
    VariableQueryType.Metrics,
    VariableQueryType.DimensionKeys,
    VariableQueryType.DimensionValues,
    VariableQueryType.EBSVolumeIDs,
    VariableQueryType.EC2InstanceAttributes,
    VariableQueryType.ResourceArns,
    VariableQueryType.LogGroups,
    VariableQueryType.Accounts,
  ].includes(parsedQuery.queryType);
  const hasAccountIDField = [
    VariableQueryType.Metrics,
    VariableQueryType.DimensionKeys,
    VariableQueryType.DimensionValues,
    VariableQueryType.LogGroups,
  ].includes(parsedQuery.queryType);
  const hasNamespaceField = [
    VariableQueryType.Metrics,
    VariableQueryType.DimensionKeys,
    VariableQueryType.DimensionValues,
  ].includes(parsedQuery.queryType);

  const styles = useStyles2(getStyles);

  return (
    <div className={styles.formStyles}>
      <VariableQueryField
        value={parsedQuery.queryType}
        options={queryTypes}
        onChange={(value: VariableQueryType) =>
          onQueryChange({ ...parsedQuery, queryType: value, accountId: undefined })
        }
        label="Query type"
        inputId={`variable-query-type-${query.refId}`}
      />
      {hasRegionField && (
        <VariableQueryField
          value={region}
          options={regions}
          onChange={(value: string) => onRegionChange(value)}
          label="Region"
          isLoading={regionIsLoading}
          inputId={`variable-query-region-${query.refId}`}
        />
      )}
      {hasAccountIDField &&
        accountState.value &&
        accountState.value?.length > 0 &&
        config.featureToggles.cloudWatchCrossAccountQuerying && (
          <VariableQueryField
            label="Account"
            value={query.accountId ?? null}
            onChange={(accountId?: string) => onQueryChange({ ...parsedQuery, accountId })}
            options={[ALL_ACCOUNTS_OPTION, ...accountState?.value]}
            allowCustomValue={false}
          />
        )}
      {hasNamespaceField && (
        <VariableQueryField
          value={namespace}
          options={namespaces}
          onChange={(value: string) => onNamespaceChange(value)}
          label="Namespace"
          inputId={`variable-query-namespace-${query.refId}`}
          allowCustomValue
        />
      )}
      {parsedQuery.queryType === VariableQueryType.DimensionValues && (
        <>
          <VariableQueryField
            value={metricName || null}
            options={metrics}
            onChange={(value: string) => onQueryChange({ ...parsedQuery, metricName: value })}
            label="Metric"
            inputId={`variable-query-metric-${query.refId}`}
            allowCustomValue
          />
          <VariableQueryField
            value={dimensionKey || null}
            options={dimensionKeys}
            onChange={(value: string) => onQueryChange({ ...parsedQuery, dimensionKey: value })}
            label="Dimension key"
            inputId={`variable-query-dimension-key-${query.refId}`}
            allowCustomValue
            error={dimensionKeyError}
          />
          <EditorField
            label="Dimensions"
            className={styles.dimensionsWidth}
            tooltip="Dimensions to filter the returned values on"
          >
            <Dimensions
              metricStat={{ ...parsedQuery, dimensions: parsedQuery.dimensionFilters }}
              onChange={(dimensions) => {
                onChange({ ...parsedQuery, dimensionFilters: dimensions });
              }}
              disableExpressions={true}
              datasource={datasource}
            />
          </EditorField>
        </>
      )}
      {parsedQuery.queryType === VariableQueryType.EBSVolumeIDs && (
        <VariableTextField
          value={query.instanceID}
          placeholder="i-XXXXXXXXXXXXXXXXX"
          onBlur={(value: string) => onQueryChange({ ...parsedQuery, instanceID: value })}
          label="Instance ID"
        />
      )}
      {parsedQuery.queryType === VariableQueryType.EC2InstanceAttributes && (
        <>
          <VariableQueryField
            value={parsedQuery.attributeName}
            options={attributeOptions}
            onChange={(value: string) => onQueryChange({ ...parsedQuery, attributeName: value })}
            label="Attribute name"
            inputId={`variable-query-instance-attribute-${query.refId}`}
            allowCustomValue
            interactive={true}
            tooltip={
              <>
                {'Attribute or tag to query on. Tags should be formatted "Tags.<name>". '}
                <TextLink
                  href="https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/template-queries-cloudwatch/#selecting-attributes"
                  external
                >
                  See the documentation for more details
                </TextLink>
              </>
            }
          />
          <EditorField
            label="Filters"
            tooltipInteractive
            tooltip={
              <>
                <TextLink
                  href="https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/template-queries-cloudwatch/#selecting-attributes"
                  external
                >
                  Pre-defined ec2:DescribeInstances filters/tags
                </TextLink>
                {' and the values to filter on. Tags should be formatted tag:<name>.'}
              </>
            }
          >
            <MultiFilter
              filters={parsedQuery.ec2Filters ?? {}}
              onChange={(filters) => {
                onChange({ ...parsedQuery, ec2Filters: filters });
              }}
              keyPlaceholder="filter/tag"
              datasource={datasource}
            />
          </EditorField>
        </>
      )}
      {parsedQuery.queryType === VariableQueryType.ResourceArns && (
        <>
          <VariableTextField
            value={parsedQuery.resourceType}
            onBlur={(value: string) => onQueryChange({ ...parsedQuery, resourceType: value })}
            label="Resource type"
          />
          <EditorField label="Tags" tooltip="Tags to filter the returned values on.">
            <MultiFilter
              filters={parsedQuery.tags}
              onChange={(filters) => {
                onChange({ ...parsedQuery, tags: filters });
              }}
              keyPlaceholder="tag"
              datasource={datasource}
            />
          </EditorField>
        </>
      )}
      {parsedQuery.queryType === VariableQueryType.LogGroups && (
        <VariableTextField
          value={query.logGroupPrefix ?? ''}
          onBlur={(value: string) => onQueryChange({ ...parsedQuery, logGroupPrefix: value })}
          label="Log group prefix"
        />
      )}
    </div>
  );
};

const getStyles = (theme: GrafanaTheme2) => ({
  formStyles: css({
    maxWidth: theme.spacing(30),
  }),
  dimensionsWidth: css({
    width: theme.spacing(50),
  }),
});
