import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'

import type { BrushEndListener } from '@elastic/charts'
import {
  Alert,
  Box,
  Card,
  Flex,
  Grid,
  Group,
  Select,
  Stack,
  Title,
  Typography,
  useMantineTheme
} from '@tidbcloud/uikit'
import { toTimeRangeValue } from '@tidbcloud/uikit/biz'
import { useElementSize } from '@tidbcloud/uikit/hooks'
import { useQuery } from 'react-query'
import { useLocation, useNavigate } from 'react-router-dom'

import { NgmClusterContext, ngmContextFromCluster } from '~/api/ngm'
import type { ClusterComputed } from '~/hooks/state'
import { useCurCluster, useCurOrg } from '~/hooks/state'
import {
  TOP_SQL_TREND_COLOR_ALPHAS,
  TOP_SQL_TREND_COLOR_NAMES,
  TOP_SQL_TREND_COLOR_SHADE,
  withAlpha
} from '~/pages/cluster/top-ru/colors'
import {
  onLegendItemOut,
  onLegendItemOver
} from '~/pages/cluster/top-ru/legendAction'
import TrendChart from '~/pages/cluster/top-ru/TrendChart'
import { ClusterStatus } from '~/utils/cluster-status'
import { eventTracking } from '~/utils/telemetry'

import {
  getStatementV2FieldValues,
  getStatementV2List,
  getStatementV2Summary,
  getStatementV2TopStats,
  statementV2ContextQueryKey
} from './api'
import ContributorCard from './ContributorCard'
import ListTable from './ListTable'
import StatementFilters from './StatementFilters'
import {
  aggregateStatementsForTable,
  buildStatementContributorSummaryFromTopstats,
  buildStatementTrendSeriesFromSummary,
  formatStatementMetricValue,
  getDefaultStatementMetric,
  getStatementMetricOptions,
  getStatementRecordKey,
  getStatementSummaryStepSeconds,
  isStatementV2RUMetricAvailable,
  normalizeStatementSummaryResponse,
  STATEMENT_V2_FIELDS,
  StatementMetricKey,
  StatementSummaryDimension
} from './statementV2'
import type {
  GetStatementV2FieldValuesParams,
  GetStatementV2TopStatsParams,
  StatementV2Dimension,
  StatementV2Field,
  StatementV2ListResponse,
  StatementV2Model
} from './types'
import { useStatementV2UrlState } from './url-state'

const FEATURE_NAME = 'SQL Statement V2'
const FIELD_VALUES_LIMIT = 2000

const EMPTY_COMPUTED: ClusterComputed = {
  isStarter: false,
  isEssential: false,
  isDedicated: false,
  isPremium: false,
  isPremiumResourcePool: false,
  isByoc: false,
  isByocResourcePool: false,
  isResourcePool: false,
  isServerless: false,
  isTiupCluster: false,
  isTiDBOperator: false
}

const uniqueStrings = (values: string[]) => [
  ...new Set(values.map((value) => value.trim()).filter(Boolean))
]

const normalizeTableNames = (values: string[]) =>
  uniqueStrings(
    values.flatMap((value) =>
      value.split(',').map((tableName) => tableName.trim())
    )
  )

function isStatementListRow(value: unknown): value is StatementV2Model {
  return (
    !!value &&
    typeof value === 'object' &&
    !Array.isArray(value) &&
    ['digest', 'digest_text', 'schema_name', 'sample_user', 'stmt_type'].some(
      (key) => key in value
    )
  )
}

function getStatementListRows(
  payload: StatementV2ListResponse | unknown
): StatementV2Model[] {
  if (Array.isArray(payload)) {
    return payload.some(isStatementListRow)
      ? (payload as StatementV2Model[])
      : []
  }
  if (!payload || typeof payload !== 'object') return []

  const record = payload as Record<string, unknown>
  for (const key of ['data', 'rows', 'items', 'list', 'result']) {
    const rows: StatementV2Model[] = getStatementListRows(record[key])
    if (rows.length) return rows
  }

  const values = Object.values(record)
  return values.length && values.every(isStatementListRow) ? values : []
}

function useStatementFieldValues(
  ctx: NgmClusterContext | undefined,
  params: GetStatementV2FieldValuesParams,
  enabled: boolean
) {
  const query = useQuery(
    [
      'statement-v2',
      'field-values',
      ...statementV2ContextQueryKey(ctx),
      params.field,
      params.begin_time,
      params.end_time
    ],
    ({ signal }) => getStatementV2FieldValues(ctx!, params, signal),
    {
      enabled: !!ctx && enabled,
      refetchOnWindowFocus: false,
      keepPreviousData: true,
      staleTime: Infinity
    }
  )

  return {
    data: Array.isArray(query.data) ? query.data : [],
    isFetching: query.isFetching,
    isError: query.isError
  }
}

function useTopStats(
  ctx: NgmClusterContext | undefined,
  params: GetStatementV2TopStatsParams,
  dimension: StatementV2Dimension,
  enabled: boolean
) {
  return useQuery(
    [
      'statement-v2',
      'topstats',
      ...statementV2ContextQueryKey(ctx),
      dimension,
      params
    ],
    ({ signal }) =>
      getStatementV2TopStats(ctx!, { ...params, dimension }, signal),
    {
      enabled: !!ctx && enabled,
      refetchOnWindowFocus: false,
      keepPreviousData: true
    }
  )
}

export interface StatementV2PageProps {
  detailPathBase?: string
}

function StatementV2Page({ detailPathBase }: StatementV2PageProps) {
  const { curOrg } = useCurOrg()
  const { curCluster } = useCurCluster()
  const location = useLocation()
  const navigate = useNavigate()
  const { colors } = useMantineTheme()
  const trendChartRef = useRef<any>(null)
  const { ref: trendChartSizeRef, width: trendChartWidth } = useElementSize()
  const computed = curCluster?.computed ?? EMPTY_COMPUTED
  const ruMetricAvailable = isStatementV2RUMetricAvailable(
    curCluster?.deployTypeV2
  )
  const stripUserPrefix = computed.isEssential
  const clusterStatus = String(curCluster?.status || '').toLowerCase()
  const statementUnavailable = [
    ClusterStatus.Paused,
    ClusterStatus.Resuming,
    ClusterStatus.Creating,
    ClusterStatus.Restoring,
    'pausing'
  ].includes(clusterStatus)
  const showCollectionHistoryNotice =
    !!curCluster?.computed.isPremium || !!curCluster?.computed.isByoc

  const {
    timeRange,
    kinds,
    search,
    dbs,
    sampleUsers,
    tableNames,
    sortRules,
    setSortRules
  } = useStatementV2UrlState()
  const [highlightedKey, setHighlightedKey] = useState<string>()
  const [metric, setMetric] = useState<StatementMetricKey>(() =>
    getDefaultStatementMetric(ruMetricAvailable)
  )
  const [trendSlice, setTrendSlice] =
    useState<StatementSummaryDimension>('digest')

  useEffect(() => {
    document.title = 'TiDB Clinic - Statement Insight (PREVIEW)'
  }, [])

  const ctx = useMemo(
    () =>
      curCluster && curOrg?.externalID
        ? ngmContextFromCluster(curCluster, curOrg.externalID)
        : undefined,
    [curCluster, curOrg?.externalID]
  )
  const queryEnabled = !!ctx && !statementUnavailable
  const queryTimeRangeValue = useMemo(
    () => toTimeRangeValue(timeRange),
    [timeRange]
  )
  const filteredKinds = useMemo(() => uniqueStrings(kinds), [kinds])
  const filteredDbs = useMemo(() => uniqueStrings(dbs), [dbs])
  const filteredUsers = useMemo(() => uniqueStrings(sampleUsers), [sampleUsers])
  const filteredTables = useMemo(
    () => normalizeTableNames(tableNames),
    [tableNames]
  )
  const tableFilterParams = useMemo(
    () =>
      filteredTables.length
        ? { field: 'table_names', values: filteredTables }
        : {},
    [filteredTables]
  )

  const listParams = useMemo(
    () => ({
      begin_time: queryTimeRangeValue[0],
      end_time: queryTimeRangeValue[1],
      schemas: filteredDbs.length ? filteredDbs : undefined,
      stmt_types: filteredKinds.length ? filteredKinds : undefined,
      sample_user: filteredUsers[0] || undefined,
      text: search || undefined,
      fields: STATEMENT_V2_FIELDS,
      ...tableFilterParams
    }),
    [
      filteredDbs,
      filteredKinds,
      filteredUsers,
      queryTimeRangeValue,
      search,
      tableFilterParams
    ]
  )
  const listQuery = useQuery(
    ['statement-v2', 'list', ...statementV2ContextQueryKey(ctx), listParams],
    ({ signal }) => getStatementV2List(ctx!, listParams, signal),
    {
      enabled: queryEnabled,
      refetchOnWindowFocus: false,
      keepPreviousData: true
    }
  )
  const listLoading = listQuery.isLoading || listQuery.isFetching
  const tableData = useMemo(
    () => aggregateStatementsForTable(getStatementListRows(listQuery.data)),
    [listQuery.data]
  )

  const metricOptions = useMemo(
    () => getStatementMetricOptions(ruMetricAvailable),
    [ruMetricAvailable]
  )
  useEffect(() => {
    if (!metricOptions.some((option) => option.value === metric)) {
      setMetric(getDefaultStatementMetric(ruMetricAvailable))
    }
  }, [metric, metricOptions, ruMetricAvailable])

  const summaryStepSeconds = useMemo(
    () =>
      getStatementSummaryStepSeconds(
        queryTimeRangeValue[1] - queryTimeRangeValue[0],
        trendChartWidth
      ),
    [queryTimeRangeValue, trendChartWidth]
  )
  const rangeKey = `${queryTimeRangeValue[0]}:${queryTimeRangeValue[1]}`
  const [openedFields, setOpenedFields] = useState<
    Partial<Record<StatementV2Field, string>>
  >({})
  const markFieldOpened = useCallback(
    (field: StatementV2Field) => {
      setOpenedFields((current) =>
        current[field] === rangeKey
          ? current
          : { ...current, [field]: rangeKey }
      )
    },
    [rangeKey]
  )
  const fieldParams = useCallback(
    (field: StatementV2Field): GetStatementV2FieldValuesParams => ({
      begin_time: queryTimeRangeValue[0],
      end_time: queryTimeRangeValue[1],
      field,
      limit: FIELD_VALUES_LIMIT
    }),
    [queryTimeRangeValue]
  )
  const stmtKinds = useStatementFieldValues(
    ctx,
    fieldParams('stmt_types'),
    queryEnabled && openedFields.stmt_types === rangeKey
  )
  const dbUsers = useStatementFieldValues(
    ctx,
    fieldParams('sample_user'),
    queryEnabled && openedFields.sample_user === rangeKey
  )
  const dbOptions = useStatementFieldValues(
    ctx,
    fieldParams('schema_name'),
    queryEnabled && openedFields.schema_name === rangeKey
  )
  const tableOptions = useStatementFieldValues(
    ctx,
    fieldParams('table_names'),
    queryEnabled && openedFields.table_names === rangeKey
  )

  const summaryParams = useMemo(
    () => ({
      begin_time: queryTimeRangeValue[0],
      end_time: queryTimeRangeValue[1],
      schemas: filteredDbs.length ? filteredDbs : undefined,
      stmt_types: filteredKinds.length ? filteredKinds : undefined,
      sample_user: filteredUsers[0] || undefined,
      text: search || undefined,
      top: '10',
      order_by: metric,
      step: summaryStepSeconds,
      dimension: trendSlice,
      ...tableFilterParams
    }),
    [
      filteredDbs,
      filteredKinds,
      filteredUsers,
      metric,
      queryTimeRangeValue,
      search,
      summaryStepSeconds,
      tableFilterParams,
      trendSlice
    ]
  )
  const summaryQuery = useQuery(
    [
      'statement-v2',
      'summary',
      ...statementV2ContextQueryKey(ctx),
      summaryParams
    ],
    ({ signal }) => getStatementV2Summary(ctx!, summaryParams, signal),
    {
      enabled: queryEnabled,
      refetchOnWindowFocus: false,
      keepPreviousData: true
    }
  )

  const topStatsParams = useMemo<GetStatementV2TopStatsParams>(
    () => ({
      begin_time: queryTimeRangeValue[0],
      end_time: queryTimeRangeValue[1],
      schemas: filteredDbs.length ? filteredDbs : undefined,
      stmt_types: filteredKinds.length ? filteredKinds : undefined,
      sample_user: filteredUsers[0] || undefined,
      text: search || undefined,
      top: '1000',
      order_by: metric,
      ...tableFilterParams
    }),
    [
      filteredDbs,
      filteredKinds,
      filteredUsers,
      metric,
      queryTimeRangeValue,
      search,
      tableFilterParams
    ]
  )
  const topUsersQuery = useTopStats(ctx, topStatsParams, 'user', queryEnabled)
  const topTypesQuery = useTopStats(
    ctx,
    topStatsParams,
    'sqltypes',
    queryEnabled
  )
  const topSqlsQuery = useTopStats(ctx, topStatsParams, 'digest', queryEnabled)
  const topDbsQuery = useTopStats(ctx, topStatsParams, 'db', queryEnabled)
  const topTablesQuery = useTopStats(ctx, topStatsParams, 'table', queryEnabled)

  const contributorSummaries = useMemo(
    () => ({
      users: buildStatementContributorSummaryFromTopstats(
        metric,
        'user',
        topUsersQuery.data?.data || [],
        stripUserPrefix
      ),
      types: buildStatementContributorSummaryFromTopstats(
        metric,
        'sqltypes',
        topTypesQuery.data?.data || []
      ),
      sqls: buildStatementContributorSummaryFromTopstats(
        metric,
        'digest',
        topSqlsQuery.data?.data || []
      ),
      dbs: buildStatementContributorSummaryFromTopstats(
        metric,
        'db',
        topDbsQuery.data?.data || []
      ),
      tables: buildStatementContributorSummaryFromTopstats(
        metric,
        'table',
        topTablesQuery.data?.data || []
      )
    }),
    [
      metric,
      stripUserPrefix,
      topDbsQuery.data,
      topSqlsQuery.data,
      topTablesQuery.data,
      topTypesQuery.data,
      topUsersQuery.data
    ]
  )
  const normalizedSummary = useMemo(
    () => normalizeStatementSummaryResponse(summaryQuery.data),
    [summaryQuery.data]
  )
  const trendSeries = useMemo(
    () =>
      buildStatementTrendSeriesFromSummary(
        normalizedSummary.items,
        queryTimeRangeValue,
        summaryStepSeconds,
        trendSlice,
        stripUserPrefix
      ),
    [
      normalizedSummary.items,
      queryTimeRangeValue,
      summaryStepSeconds,
      stripUserPrefix,
      trendSlice
    ]
  )

  const contributorLoading = [
    topUsersQuery,
    topTypesQuery,
    topSqlsQuery,
    topDbsQuery,
    topTablesQuery
  ].some((query) => query.isFetching)
  const trendLoading = summaryQuery.isFetching
  const coreQueryError = [
    listQuery,
    summaryQuery,
    topUsersQuery,
    topTypesQuery,
    topSqlsQuery,
    topDbsQuery,
    topTablesQuery,
    stmtKinds,
    dbUsers,
    dbOptions,
    tableOptions
  ].some((query) => query.isError)

  const trendColorPalette = useMemo(
    () =>
      TOP_SQL_TREND_COLOR_ALPHAS.flatMap((alpha) =>
        TOP_SQL_TREND_COLOR_NAMES.map((name) =>
          withAlpha(colors[name][TOP_SQL_TREND_COLOR_SHADE], alpha)
        )
      ),
    [colors]
  )
  const seriesColorMap = useMemo(
    () =>
      trendSeries.reduce<Record<string, string>>((map, series, index) => {
        map[series.key] = trendColorPalette[index % trendColorPalette.length]
        return map
      }, {}),
    [trendColorPalette, trendSeries]
  )
  const coloredTrendSeries = useMemo(
    () =>
      trendSeries.map((series, index) => ({
        ...series,
        color:
          seriesColorMap[series.key] ||
          trendColorPalette[index % trendColorPalette.length]
      })),
    [seriesColorMap, trendColorPalette, trendSeries]
  )
  const tableSeriesColorMap = useMemo(
    () =>
      tableData.reduce<Record<string, string>>(
        (map, row, index) => {
          const color = trendColorPalette[index % trendColorPalette.length]
          map[getStatementRecordKey(row)] ||= color
          if (row.digest) map[row.digest] ||= color
          return map
        },
        { ...seriesColorMap }
      ),
    [seriesColorMap, tableData, trendColorPalette]
  )

  const handleBrushEnd: BrushEndListener = (event) => {
    if (event.x) eventTracking(`${FEATURE_NAME} Trend Brush Changed`)
  }
  const handleTableHover = (rowKey: string) => {
    setHighlightedKey(rowKey)
    onLegendItemOver(trendChartRef.current, rowKey)
  }
  const handleTableLeave = () => {
    setHighlightedKey(undefined)
    onLegendItemOut(trendChartRef.current)
  }
  const handleRowClick = (row: StatementV2Model) => {
    const detailPath =
      detailPathBase || `${location.pathname.replace(/\/$/, '')}/detail`
    const params = new URLSearchParams({
      digest: row.digest ?? '',
      schema_name: row.schema_name ?? '',
      begin_time: String(queryTimeRangeValue[0]),
      end_time: String(queryTimeRangeValue[1]),
      source: 'statement-v2'
    })
    navigate(`${detailPath}?${params}`, {
      state: {
        historyBack: true,
        detailFromHref: `${location.pathname}${location.search}`
      }
    })
    eventTracking(`${FEATURE_NAME} List Table Row Clicked`)
  }

  if (statementUnavailable) {
    return (
      <Stack h='calc(100vh - 220px)' gap='md'>
        <Title order={2}>Statement Insight (PREVIEW)</Title>
        <Card shadow='none' style={{ flex: 1 }}>
          <Stack align='center' justify='center' h='100%'>
            <Typography variant='body-lg' c='carbon.7' ta='center'>
              Statement Insight is unavailable while this cluster is{' '}
              {clusterStatus || 'not ready'}.
            </Typography>
          </Stack>
        </Card>
      </Stack>
    )
  }

  return (
    <Stack mx={-24} gap='md' data-testid='statement-v2-page'>
      <Title order={2} mx={24}>
        Statement Insight (PREVIEW)
      </Title>
      {showCollectionHistoryNotice && (
        <Alert ml={24} mb={8} color='peacock'>
          <Typography variant='body-lg' pr={8}>
            Data collection started when Statement Insight was enabled. Your
            available history grows day by day, currently no historical
            backfill.
          </Typography>
        </Alert>
      )}

      {coreQueryError && (
        <Alert mx={24} color='red' title='Unable to load Statement Insight'>
          Some statement data could not be loaded. Change a filter or refresh
          the page to try again.
        </Alert>
      )}

      <Flex px={24} justify='flex-start' wrap='wrap' gap='md'>
        <StatementFilters
          isLoading={listLoading || contributorLoading}
          stmtKindOptions={stmtKinds.data}
          dbUserOptions={dbUsers.data}
          dbOptions={dbOptions.data}
          tableOptions={tableOptions.data}
          stmtKindsLoading={stmtKinds.isFetching}
          dbUsersLoading={dbUsers.isFetching}
          dbsLoading={dbOptions.isFetching}
          tablesLoading={tableOptions.isFetching}
          onStmtKindsOpen={() => markFieldOpened('stmt_types')}
          onDbUsersOpen={() => markFieldOpened('sample_user')}
          onDbsOpen={() => markFieldOpened('schema_name')}
          onTablesOpen={() => markFieldOpened('table_names')}
          stripUserPrefix={stripUserPrefix}
        />
      </Flex>

      <Card
        withBorder
        mx={24}
        p='md'
        data-testid='statement-v2-contributors-card'
      >
        <Stack gap='lg'>
          <Group justify='space-between' align='center'>
            <Typography variant='title-lg'>Top Contributors</Typography>
            <Group gap='xs' align='center'>
              <Typography variant='label-md' c='carbon.7'>
                Measured by
              </Typography>
              <Select
                w={160}
                size='sm'
                value={metric}
                data={metricOptions}
                disabled={listLoading || contributorLoading}
                onChange={(value) => {
                  if (!value) return
                  setMetric(value as StatementMetricKey)
                  eventTracking(`${FEATURE_NAME} Metric Changed`, {
                    metric: value
                  })
                }}
              />
            </Group>
          </Group>

          <Grid gutter='lg'>
            <Grid.Col span={{ base: 12, lg: 3 }}>
              <ContributorCard
                title='DB Users'
                total={contributorSummaries.users.totalCount}
                metric={metric}
                totalValue={contributorSummaries.users.totalValue}
                items={contributorSummaries.users.items}
                loading={contributorLoading}
                testId='statement-v2-users-card'
              />
            </Grid.Col>
            <Grid.Col span={{ base: 12, lg: 3 }}>
              <ContributorCard
                title='SQL Types'
                total={contributorSummaries.types.totalCount}
                metric={metric}
                totalValue={contributorSummaries.types.totalValue}
                items={contributorSummaries.types.items}
                loading={contributorLoading}
                testId='statement-v2-types-card'
              />
            </Grid.Col>
            <Grid.Col span={{ base: 12, lg: 6 }}>
              <ContributorCard
                title='SQL Template'
                total={contributorSummaries.sqls.totalCount}
                metric={metric}
                totalValue={contributorSummaries.sqls.totalValue}
                items={contributorSummaries.sqls.items}
                loading={contributorLoading}
                testId='statement-v2-sql-card'
                variant='sql'
              />
            </Grid.Col>
          </Grid>

          <Grid gutter='lg'>
            <Grid.Col span={{ base: 12, lg: 3 }}>
              <ContributorCard
                title='DBs'
                total={contributorSummaries.dbs.totalCount}
                metric={metric}
                totalValue={contributorSummaries.dbs.totalValue}
                items={contributorSummaries.dbs.items}
                loading={contributorLoading}
                testId='statement-v2-dbs-card'
              />
            </Grid.Col>
            <Grid.Col span={{ base: 12, lg: 3 }}>
              <ContributorCard
                title='Tables'
                total={contributorSummaries.tables.totalCount}
                metric={metric}
                totalValue={contributorSummaries.tables.totalValue}
                items={contributorSummaries.tables.items}
                loading={contributorLoading}
                testId='statement-v2-tables-card'
              />
            </Grid.Col>
          </Grid>

          <Box
            pt='lg'
            sx={{ borderTop: '1px solid var(--mantine-color-carbon-3)' }}
            data-testid='statement-v2-trend-card'
          >
            <Stack gap='lg'>
              <Group justify='space-between' align='center'>
                <Typography variant='title-lg'>
                  Resource Usage Over Time
                </Typography>
                <Group gap='xs' align='center'>
                  <Typography variant='label-md' c='carbon.7'>
                    Sliced by
                  </Typography>
                  <Select
                    w={120}
                    size='sm'
                    value={trendSlice}
                    onChange={(value) => {
                      if (!value) return
                      setTrendSlice(value as StatementSummaryDimension)
                      eventTracking(`${FEATURE_NAME} Trend Slice Changed`, {
                        slice: value
                      })
                    }}
                    data={[
                      { label: 'SQL', value: 'digest' },
                      { label: 'User', value: 'user' },
                      { label: 'SQL Type', value: 'sqltypes' },
                      { label: 'DB', value: 'db' },
                      { label: 'Table', value: 'table' }
                    ]}
                  />
                </Group>
              </Group>

              <Box
                ref={trendChartSizeRef}
                h={320}
                w='100%'
                sx={{ minWidth: 0, display: 'flex' }}
              >
                <TrendChart
                  ref={trendChartRef}
                  data={trendLoading ? [] : coloredTrendSeries}
                  loading={trendLoading}
                  timeRangeValue={queryTimeRangeValue}
                  stepSeconds={summaryStepSeconds}
                  valueFormatter={(value) =>
                    formatStatementMetricValue(metric, value)
                  }
                  highlightedKey={highlightedKey}
                  onBrushEnd={handleBrushEnd}
                />
              </Box>
            </Stack>
          </Box>
        </Stack>
      </Card>

      <ListTable
        data={tableData}
        loading={listLoading}
        stripUserPrefix={stripUserPrefix}
        ruMetricAvailable={ruMetricAvailable}
        sortBy={sortRules}
        onSortChange={setSortRules}
        seriesColorMap={tableSeriesColorMap}
        highlightedKey={highlightedKey}
        onRowHover={handleTableHover}
        onRowLeave={handleTableLeave}
        onRowClick={handleRowClick}
      />
    </Stack>
  )
}

export default React.memo(StatementV2Page)
