import {CSSProperties, Fragment, useCallback, useMemo, useState} from 'react';
import {browserHistory} from 'react-router';
import styled from '@emotion/styled';

import {Button} from 'sentry/components/button';
import Count from 'sentry/components/count';
import EmptyStateWarning from 'sentry/components/emptyStateWarning';
import IdBadge from 'sentry/components/idBadge';
import Link from 'sentry/components/links/link';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import Pagination from 'sentry/components/pagination';
import PerformanceDuration from 'sentry/components/performanceDuration';
import ScoreBar from 'sentry/components/scoreBar';
import TextOverflow from 'sentry/components/textOverflow';
import {Tooltip} from 'sentry/components/tooltip';
import {CHART_PALETTE} from 'sentry/constants/chartPalette';
import {IconChevron, IconWarning} from 'sentry/icons';
import {t, tct} from 'sentry/locale';
import {space} from 'sentry/styles/space';
import {EventsResultsDataRow} from 'sentry/utils/profiling/hooks/types';
import {useProfileFunctions} from 'sentry/utils/profiling/hooks/useProfileFunctions';
import {generateProfileFlamechartRouteWithQuery} from 'sentry/utils/profiling/routes';
import {decodeScalar} from 'sentry/utils/queryString';
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
import useProjects from 'sentry/utils/useProjects';

import {
  Accordion,
  AccordionItem,
  ContentContainer,
  HeaderContainer,
  HeaderTitleLegend,
  StatusContainer,
  Subtitle,
  WidgetContainer,
} from './styles';

const MAX_FUNCTIONS = 3;
const CURSOR_NAME = 'slowFnCursor';

interface SlowestFunctionsWidgetProps {
  userQuery?: string;
}

export function SlowestFunctionsWidget({userQuery}: SlowestFunctionsWidgetProps) {
  const location = useLocation();

  const [expandedIndex, setExpandedIndex] = useState(0);

  const slowFnCursor = useMemo(
    () => decodeScalar(location.query[CURSOR_NAME]),
    [location.query]
  );

  const handleCursor = useCallback((cursor, pathname, query) => {
    browserHistory.push({
      pathname,
      query: {...query, [CURSOR_NAME]: cursor},
    });
  }, []);

  const functionsQuery = useProfileFunctions<FunctionsField>({
    fields: functionsFields,
    referrer: 'api.profiling.suspect-functions.list',
    sort: {
      key: 'sum()',
      order: 'desc',
    },
    query: userQuery,
    limit: MAX_FUNCTIONS,
    cursor: slowFnCursor,
  });

  const hasFunctions = (functionsQuery.data?.data?.length || 0) > 0;

  const totalsQuery = useProfileFunctions<TotalsField>({
    fields: totalsFields,
    referrer: 'api.profiling.suspect-functions.totals',
    sort: {
      key: 'sum()',
      order: 'desc',
    },
    query: userQuery,
    limit: MAX_FUNCTIONS,
    // make sure to query for the projects from the top functions
    projects: functionsQuery.isFetched
      ? [
          ...new Set(
            (functionsQuery.data?.data ?? []).map(func => func['project.id'] as number)
          ),
        ]
      : [],
    enabled: functionsQuery.isFetched && hasFunctions,
  });

  const isLoading = functionsQuery.isLoading || (hasFunctions && totalsQuery.isLoading);
  const isError = functionsQuery.isError || totalsQuery.isError;

  return (
    <WidgetContainer>
      <HeaderContainer>
        <HeaderTitleLegend>{t('Suspect Functions')}</HeaderTitleLegend>
        <Subtitle>{t('Slowest functions by total time spent.')}</Subtitle>
        <StyledPagination
          pageLinks={functionsQuery.getResponseHeader?.('Link') ?? null}
          size="xs"
          onCursor={handleCursor}
        />
      </HeaderContainer>
      <ContentContainer>
        {isLoading && (
          <StatusContainer>
            <LoadingIndicator />
          </StatusContainer>
        )}
        {isError && (
          <StatusContainer>
            <IconWarning data-test-id="error-indicator" color="gray300" size="lg" />
          </StatusContainer>
        )}
        {!isError && !isLoading && !hasFunctions && (
          <EmptyStateWarning>
            <p>{t('No functions found')}</p>
          </EmptyStateWarning>
        )}
        {hasFunctions && totalsQuery.isFetched && (
          <Accordion>
            {(functionsQuery.data?.data ?? []).map((f, i) => {
              const projectEntry = totalsQuery.data?.data?.find(
                row => row['project.id'] === f['project.id']
              );
              const projectTotalDuration = projectEntry?.['sum()'] ?? f['sum()'];
              return (
                <SlowestFunctionEntry
                  key={`${f['project.id']}-${f.package}-${f.function}`}
                  isExpanded={i === expandedIndex}
                  setExpanded={() => setExpandedIndex(i)}
                  func={f}
                  totalDuration={projectTotalDuration as number}
                  query={userQuery ?? ''}
                />
              );
            })}
          </Accordion>
        )}
      </ContentContainer>
    </WidgetContainer>
  );
}

interface SlowestFunctionEntryProps {
  func: EventsResultsDataRow<FunctionsField>;
  isExpanded: boolean;
  query: string;
  setExpanded: () => void;
  totalDuration: number;
}

const BARS = 10;

function SlowestFunctionEntry({
  func,
  isExpanded,
  query,
  setExpanded,
  totalDuration,
}: SlowestFunctionEntryProps) {
  const organization = useOrganization();

  const {projects} = useProjects();
  const project = projects.find(p => p.id === String(func['project.id']));

  const score = Math.ceil((((func['sum()'] as number) ?? 0) / totalDuration) * BARS);
  const palette = new Array(BARS).fill([CHART_PALETTE[0][0]]);

  const userQuery = useMemo(() => {
    const conditions = new MutableSearch(query);

    conditions.setFilterValues('project.id', [String(func['project.id'])]);
    conditions.setFilterValues('package', [String(func.package)]);
    conditions.setFilterValues('function', [String(func.function)]);

    return conditions.formatString();
  }, [func, query]);

  const functionTransactionsQuery = useProfileFunctions<FunctionTransactionField>({
    fields: functionTransactionsFields,
    referrer: 'api.profiling.suspect-functions.transactions',
    sort: {
      key: 'sum()',
      order: 'desc',
    },
    query: userQuery,
    limit: 5,
    enabled: isExpanded,
  });

  return (
    <Fragment>
      <AccordionItem>
        {project && (
          <Tooltip title={project.name}>
            <IdBadge project={project} avatarSize={16} hideName />
          </Tooltip>
        )}
        <FunctionName>
          <Tooltip title={func.package}>{func.function}</Tooltip>
        </FunctionName>
        <Tooltip
          title={tct('Appeared [count] times for a total self time of [totalSelfTime]', {
            count: <Count value={func['count()'] as number} />,
            totalSelfTime: (
              <PerformanceDuration nanoseconds={func['sum()'] as number} abbreviation />
            ),
          })}
        >
          <ScoreBar score={score} palette={palette} size={20} radius={0} />
        </Tooltip>
        <Button
          icon={<IconChevron size="xs" direction={isExpanded ? 'up' : 'down'} />}
          aria-label={t('Expand')}
          aria-expanded={isExpanded}
          size="zero"
          borderless
          onClick={() => setExpanded()}
        />
      </AccordionItem>
      {isExpanded && (
        <Fragment>
          {functionTransactionsQuery.isError && (
            <StatusContainer>
              <IconWarning data-test-id="error-indicator" color="gray300" size="lg" />
            </StatusContainer>
          )}
          {functionTransactionsQuery.isLoading && (
            <StatusContainer>
              <LoadingIndicator />
            </StatusContainer>
          )}
          {functionTransactionsQuery.isFetched && (
            <TransactionsList data-test-id="transactions-list">
              <TransactionsListHeader>
                <TextOverflow>{t('Transaction')}</TextOverflow>
              </TransactionsListHeader>
              <TransactionsListHeader align="right">
                <TextOverflow>{t('Count')}</TextOverflow>
              </TransactionsListHeader>
              <TransactionsListHeader align="right">
                <TextOverflow>{t('Total Self Time')}</TextOverflow>
              </TransactionsListHeader>
              {(functionTransactionsQuery.data?.data ?? []).map(transaction => {
                const examples = transaction['examples()'] as string[];
                let transactionCol = <Fragment>{transaction.transaction}</Fragment>;

                if (project && examples.length) {
                  const target = generateProfileFlamechartRouteWithQuery({
                    orgSlug: organization.slug,
                    projectSlug: project.slug,
                    profileId: examples[0],
                    query: {
                      frameName: func.function as string,
                      framePackage: func.package as string,
                    },
                  });
                  transactionCol = <Link to={target}>{transactionCol}</Link>;
                }

                return (
                  <Fragment key={transaction.transaction as string}>
                    <TransactionsListCell>
                      <TextOverflow>{transactionCol}</TextOverflow>
                    </TransactionsListCell>
                    <TransactionsListCell align="right">
                      <Count value={transaction['count()'] as number} />
                    </TransactionsListCell>
                    <TransactionsListCell align="right">
                      <PerformanceDuration
                        nanoseconds={transaction['sum()'] as number}
                        abbreviation
                      />
                    </TransactionsListCell>
                  </Fragment>
                );
              })}
            </TransactionsList>
          )}
        </Fragment>
      )}
    </Fragment>
  );
}

const functionsFields = [
  'project.id',
  'package',
  'function',
  'count()',
  'sum()',
] as const;

type FunctionsField = (typeof functionsFields)[number];

const totalsFields = ['project.id', 'sum()'] as const;

type TotalsField = (typeof totalsFields)[number];

const functionTransactionsFields = [
  'transaction',
  'count()',
  'sum()',
  'examples()',
] as const;

type FunctionTransactionField = (typeof functionTransactionsFields)[number];

const StyledPagination = styled(Pagination)`
  margin: 0;
`;

const FunctionName = styled(TextOverflow)`
  flex: 1 1 auto;
`;

const TransactionsList = styled('div')`
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: 65% 10% 25%;
  grid-template-rows: 18px auto auto auto auto auto;
  padding: ${space(0)} ${space(2)};
`;

const TransactionsListHeader = styled('span')<{
  align?: CSSProperties['textAlign'];
}>`
  text-transform: uppercase;
  font-size: ${p => p.theme.fontSizeExtraSmall};
  font-weight: 600;
  color: ${p => p.theme.subText};
  text-align: ${p => p.align};
`;

const TransactionsListCell = styled('div')<{align?: CSSProperties['textAlign']}>`
  font-size: ${p => p.theme.fontSizeSmall};
  text-align: ${p => p.align};
  padding: ${space(0.5)} 0px;
`;
