import {Fragment, useMemo} from 'react';
import type {InjectedRouter} from 'react-router';
import styled from '@emotion/styled';
import {ErrorBoundary} from '@sentry/react';
import type {Location} from 'history';

import ErrorPanel from 'sentry/components/charts/errorPanel';
import {HeaderTitle} from 'sentry/components/charts/styles';
import {EquationFormatter} from 'sentry/components/metrics/equationInput/syntax/formatter';
import TextOverflow from 'sentry/components/textOverflow';
import {IconWarning} from 'sentry/icons';
import {t} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import type {PageFilters} from 'sentry/types/core';
import type {Organization} from 'sentry/types/organization';
import {getFormattedMQL, unescapeMetricsFormula} from 'sentry/utils/metrics';
import {hasMetricsNewInputs} from 'sentry/utils/metrics/features';
import {formatMRIField, MRIToField, parseMRI} from 'sentry/utils/metrics/mri';
import {MetricExpressionType} from 'sentry/utils/metrics/types';
import {useMetricsQuery} from 'sentry/utils/metrics/useMetricsQuery';
import {useVirtualMetricsContext} from 'sentry/utils/metrics/virtualMetricsContext';
import {MetricBigNumberContainer} from 'sentry/views/dashboards/metrics/bigNumber';
import {MetricChartContainer} from 'sentry/views/dashboards/metrics/chart';
import {MetricTableContainer} from 'sentry/views/dashboards/metrics/table';
import type {DashboardMetricsExpression} from 'sentry/views/dashboards/metrics/types';
import {
  expressionsToApiQueries,
  formatAlias,
  getMetricExpressions,
  isMetricsEquation,
  toMetricDisplayType,
} from 'sentry/views/dashboards/metrics/utils';
import type {DashboardFilters, Widget} from 'sentry/views/dashboards/types';
import {DisplayType} from 'sentry/views/dashboards/types';
import {WidgetCardPanel, WidgetTitleRow} from 'sentry/views/dashboards/widgetCard';
import {DashboardsMEPContext} from 'sentry/views/dashboards/widgetCard/dashboardsMEPContext';
import {Toolbar} from 'sentry/views/dashboards/widgetCard/toolbar';
import WidgetCardContextMenu from 'sentry/views/dashboards/widgetCard/widgetCardContextMenu';
import {useMetricsIntervalOptions} from 'sentry/views/metrics/utils/useMetricsIntervalParam';

type Props = {
  isEditingDashboard: boolean;
  location: Location;
  organization: Organization;
  router: InjectedRouter;
  selection: PageFilters;
  widget: Widget;
  dashboardFilters?: DashboardFilters;
  index?: string;
  isMobile?: boolean;
  onDelete?: () => void;
  onDuplicate?: () => void;
  onEdit?: (index: string) => void;
  renderErrorMessage?: (errorMessage?: string) => React.ReactNode;
  showContextMenu?: boolean;
};

const EMPTY_FN = () => {};

export function getWidgetTitle(expressions: DashboardMetricsExpression[]) {
  const filteredExpressions = expressions.filter(query => !query.isQueryOnly);

  if (filteredExpressions.length === 1) {
    const firstQuery = filteredExpressions[0];
    if (isMetricsEquation(firstQuery)) {
      return (
        <Fragment>
          <EquationFormatter equation={unescapeMetricsFormula(firstQuery.formula)} />
        </Fragment>
      );
    }
    return formatAlias(firstQuery.alias) ?? getFormattedMQL(firstQuery);
  }

  return filteredExpressions
    .map(q =>
      isMetricsEquation(q)
        ? formatAlias(q.alias) ?? unescapeMetricsFormula(q.formula)
        : formatAlias(q.alias) ?? formatMRIField(MRIToField(q.mri, q.aggregation))
    )
    .join(', ');
}

export function MetricWidgetCard({
  organization,
  selection,
  widget,
  isEditingDashboard,
  onDelete,
  onDuplicate,
  location,
  router,
  dashboardFilters,
  renderErrorMessage,
  showContextMenu = true,
}: Props) {
  const metricsNewInputs = hasMetricsNewInputs(organization);
  const {getVirtualMRIQuery, isLoading: isLoadingVirtualMetrics} =
    useVirtualMetricsContext();

  const metricExpressions = getMetricExpressions(
    widget,
    dashboardFilters,
    getVirtualMRIQuery
  );

  const hasSetMetric = useMemo(
    () =>
      metricExpressions.some(
        expression =>
          expression.type === MetricExpressionType.QUERY &&
          parseMRI(expression.mri)!.type === 's'
      ),
    [metricExpressions]
  );

  const widgetMQL = useMemo(
    () => (isLoadingVirtualMetrics ? '' : getWidgetTitle(metricExpressions)),
    [isLoadingVirtualMetrics, metricExpressions]
  );

  const metricQueries = useMemo(() => {
    const formattedAliasQueries = expressionsToApiQueries(
      metricExpressions,
      metricsNewInputs
    ).map(query => {
      if (query.alias) {
        return {...query, alias: formatAlias(query.alias)};
      }
      return query;
    });
    return [...formattedAliasQueries];
  }, [metricExpressions, metricsNewInputs]);

  const {interval: validatedInterval} = useMetricsIntervalOptions({
    // TODO: Figure out why this can be undefined
    interval: widget.interval ?? '',
    hasSetMetric,
    datetime: selection.datetime,
    onIntervalChange: EMPTY_FN,
  });

  const {
    data: timeseriesData,
    isLoading,
    isError,
    error,
  } = useMetricsQuery(metricQueries, selection, {
    interval: validatedInterval,
  });

  const vizualizationComponent = useMemo(() => {
    if (widget.displayType === DisplayType.TABLE) {
      return (
        <MetricTableContainer
          metricQueries={metricQueries}
          timeseriesData={timeseriesData}
          isLoading={isLoading}
        />
      );
    }
    if (widget.displayType === DisplayType.BIG_NUMBER) {
      return (
        <MetricBigNumberContainer timeseriesData={timeseriesData} isLoading={isLoading} />
      );
    }

    return (
      <MetricChartContainer
        timeseriesData={timeseriesData}
        isLoading={isLoading}
        metricQueries={metricQueries}
        displayType={toMetricDisplayType(widget.displayType)}
        chartHeight={!showContextMenu ? 200 : undefined}
        showLegend
      />
    );
  }, [widget.displayType, metricQueries, timeseriesData, isLoading, showContextMenu]);

  return (
    <DashboardsMEPContext.Provider
      value={{
        isMetricsData: undefined,
        setIsMetricsData: () => {},
      }}
    >
      <WidgetCardPanel isDragging={false}>
        <WidgetHeaderWrapper>
          <WidgetHeaderDescription>
            <WidgetTitleRow>
              <WidgetTitle>
                <TextOverflow>{widget.title || widgetMQL}</TextOverflow>
              </WidgetTitle>
            </WidgetTitleRow>
          </WidgetHeaderDescription>

          <ContextMenuWrapper>
            {showContextMenu && !isEditingDashboard && (
              <WidgetCardContextMenu
                organization={organization}
                widget={widget}
                selection={selection}
                showContextMenu
                isPreview={false}
                widgetLimitReached={false}
                onEdit={() => {
                  router.push({
                    pathname: `${location.pathname}${
                      location.pathname.endsWith('/') ? '' : '/'
                    }widget/${widget.id}/`,
                    query: location.query,
                  });
                }}
                router={router}
                location={location}
                onDelete={onDelete}
                onDuplicate={onDuplicate}
              />
            )}
          </ContextMenuWrapper>
        </WidgetHeaderWrapper>
        <ErrorBoundary>
          <WidgetCardBody
            isError={isError}
            timeseriesData={timeseriesData}
            renderErrorMessage={renderErrorMessage}
            error={error}
          >
            {vizualizationComponent}
          </WidgetCardBody>
        </ErrorBoundary>
        {isEditingDashboard && <Toolbar onDelete={onDelete} onDuplicate={onDuplicate} />}
      </WidgetCardPanel>
    </DashboardsMEPContext.Provider>
  );
}

function WidgetCardBody({children, isError, timeseriesData, renderErrorMessage, error}) {
  if (isError && !timeseriesData) {
    const errorMessage =
      error?.responseJSON?.detail?.toString() || t('Error while fetching metrics data');
    return (
      <ErrorWrapper>
        {renderErrorMessage?.(errorMessage)}
        <ErrorPanel>
          <IconWarning color="gray500" size="lg" />
        </ErrorPanel>
      </ErrorWrapper>
    );
  }

  return children;
}

const WidgetHeaderWrapper = styled('div')`
  min-height: 36px;
  width: 100%;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
`;

const ContextMenuWrapper = styled('div')`
  padding: ${space(2)} ${space(1)} 0 ${space(3)};
`;

const WidgetHeaderDescription = styled('div')`
  ${p => p.theme.overflowEllipsis};
  overflow-y: visible;
`;

const WidgetTitle = styled(HeaderTitle)`
  padding-left: ${space(3)};
  padding-top: ${space(2)};
  padding-right: ${space(1)};
  ${p => p.theme.overflowEllipsis};
  font-weight: ${p => p.theme.fontWeightNormal};
`;

const ErrorWrapper = styled('div')`
  padding-top: ${space(1)};
`;