import { eachDayOfInterval } from 'date-fns'
import _ from 'lodash'
import { useCallback, useMemo, useRef, useState } from 'react'
import { ScrollView, TouchableOpacity, View } from 'react-native'

import { sourceColorsByDsp } from '../Colors'
import {
  SourceLean,
  sourceLeans,
  sourceOrder,
  StreamingDsp,
  VideoDsp,
} from '../Consts'
import { NavigationProp } from '../Navigation'
import { CenteredActivityIndicator } from '../components/CenteredActivityIndicator'
import { CommonScreenContainer } from '../components/CommonScreenContainer'
import { ErrorBoundary } from '../components/ErrorBoundary'
import { GraphDetailScreenHeader } from '../components/GraphDetailScreenHeader'
import { HeaderNav } from '../components/HeaderNav'
import { MarketSelectorButton } from '../components/MarketSelectorButton'
import { SourceBreakdownAreaGraph } from '../components/SourceBreakdownAreaGraph'
import { TimeIntervalSelector } from '../components/TimeIntervalSelector'
import { TrackStreamSourcesTable } from '../components/TrackStreamsSourcesTable'
import { ScreenNames } from '../constants/ScreenNames'
import { ScreenContext } from '../contexts/ScreenContext'
import { useComponentLayout } from '../hooks/useComponentLayout'
import { NetworkError } from '../hooks/useNetwork'
import { useScreenRefreshControl } from '../hooks/useScreenRefreshControl'
import { Strings } from '../i18n'
import ArrowDown from '../icons/ArrowDown'
import { TimeInterval, timeIntervals } from '../types/TimeInterval'
import { DspSource, DspSourcesByDate } from '../types/types'
import { notEmpty, timeRangeInterval } from '../util'
import { formatDateString, formatInterval } from '../util/date'

function getDspData(
  sources: DspSourcesByDate,
  dsp: StreamingDsp | VideoDsp
): DspSource[] {
  // Get data for each date for the selected dsp, filter out dates for which there is no data
  return sources.map(({ data }) => data?.[dsp]).filter(notEmpty)
}

function leanListSections(
  dspData: DspSource[],
  sourcesForLeans: { lean: SourceLean; sources: string[] }[]
) {
  // Sum of streams for a particular lean and source
  function streamsSumForLeanAndSource(lean: SourceLean, source: string) {
    return _.sum(
      dspData
        .map((x) => x[lean])
        .flatMap((r) => Object.entries(r))
        .filter(([s]) => s === source)
        .map(([, value]) => value)
    )
  }

  // Sum of streams for a particular lean
  function streamsSumForLean(lean: SourceLean) {
    return _.sum(
      dspData
        .map((x) => x[lean])
        .flatMap((r) => Object.entries(r))
        .map(([, value]) => value)
    )
  }

  // Sum of all streams across all leans and sources
  function streamsTotal() {
    return _.sum(
      dspData
        .flatMap((x) => Object.entries(x).map(([, sources]) => sources))
        .flatMap((r) => Object.entries(r))
        .map(([, value]) => value)
    )
  }

  const totalSum = streamsTotal()

  return sourcesForLeans.map(({ lean, sources }) => {
    const streams = streamsSumForLean(lean)
    const percentage = totalSum !== 0 ? (streams / totalSum) * 100 : 0
    return {
      lean: {
        type: lean,
        streams,
        percentage,
      },
      sources: sources.map((source) => {
        const streams = streamsSumForLeanAndSource(lean, source)
        const percentage = totalSum !== 0 ? (streams / totalSum) * 100 : 0

        return {
          source,
          streams,
          percentage,
        }
      }),
    }
  })
}

function getGraphData(
  sources: DspSourcesByDate,
  dsp: StreamingDsp | VideoDsp,
  interval: Interval
): {
  date: string
  sources: Record<string, number | null>
}[] {
  const sourcesForDspByDate: Record<
    string,
    Record<string, number | null>
  > = Object.fromEntries(
    sources.map(({ date, data }) => [
      date,
      data != null
        ? { ...data?.[dsp]?.leanBack, ...data?.[dsp]?.leanForward }
        : {},
    ])
  )

  const daysInInterval = eachDayOfInterval(interval)

  return daysInInterval.flatMap((d) => {
    const dateString = formatDateString(d)
    const sources = sourcesForDspByDate[dateString]
    return sources != null
      ? [
          {
            date: formatDateString(d),
            sources,
          },
        ]
      : []
  })
}

interface Props {
  screenName: string
  screenTitle?: string
  headerImage: React.ReactNode
  dsp: StreamingDsp | VideoDsp
  countryCode: string
  sources?: DspSourcesByDate
  selectedTimeInterval: TimeInterval
  onTimeIntervalChange: (timeInterval: TimeInterval) => void
  error: NetworkError | null
  showToast: boolean
  navigation: NavigationProp<ScreenNames>
  retry: () => void
  refreshing: boolean
  onPressMarketSelectorButton?: () => void
}

export const EntityStreamsSourceScreenContent: React.FC<Props> = ({
  screenName,
  screenTitle,
  headerImage,
  dsp,
  countryCode,
  sources: sourcesForInterval,
  selectedTimeInterval,
  onTimeIntervalChange,
  error,
  showToast,
  navigation,
  retry,
  refreshing,
  onPressMarketSelectorButton,
}) => {
  const [selectedDate, setSelectedDate] = useState<string | null>(null)
  const selectedTimeRangeInterval = timeRangeInterval(selectedTimeInterval)

  const { refreshControl, screenContext } = useScreenRefreshControl(
    retry,
    refreshing
  )

  const [onScrollViewLayout, scrollViewLayout] = useComponentLayout()
  const [scrollEnabled, setScrollEnabled] = useState<boolean>(true)

  const [onGraphDetailScreenHeaderLayout, graphDetailScreenHeaderLayout] =
    useComponentLayout()

  const [displayDownArrow, setDisplayDownArrow] = useState<boolean>(true)
  const onScrollViewScroll = useCallback(() => {
    setDisplayDownArrow(false)
  }, [])
  const scrollViewRef = useRef<ScrollView>(null)

  const tableData = useMemo(() => {
    const sourcesForLeans =
      sourcesForInterval != null
        ? sourceLeans.map((lean) => ({
            lean,
            sources: _.uniq(
              sourcesForInterval.flatMap(({ data }) =>
                Object.keys(data?.[dsp]?.[lean] ?? {})
              )
            ),
          }))
        : null

    const sources =
      selectedDate == null
        ? sourcesForInterval
        : sourcesForInterval?.filter(({ date }) => selectedDate === date)

    const dspData = sources != null ? getDspData(sources, dsp) : null

    const tableData =
      dspData != null && sourcesForLeans != null
        ? leanListSections(dspData, sourcesForLeans)
        : null

    return tableData
  }, [sourcesForInterval, dsp, selectedDate])

  const graphData = useMemo(() => {
    return sourcesForInterval != null
      ? getGraphData(sourcesForInterval, dsp, selectedTimeRangeInterval)
      : null
  }, [sourcesForInterval, dsp, selectedTimeRangeInterval])

  return (
    <ScreenContext.Provider value={screenContext}>
      <CommonScreenContainer screenName={screenName}>
        <HeaderNav
          title={screenTitle}
          showToast={showToast}
          navigation={navigation}
          style={{
            zIndex: 10,
          }}
          right={
            onPressMarketSelectorButton != null ? (
              <MarketSelectorButton
                countryCode={countryCode}
                onPress={onPressMarketSelectorButton}
              />
            ) : null
          }
        />
        <ErrorBoundary error={error} refreshControl={refreshControl}>
          <ScrollView
            style={{
              paddingHorizontal: 16,
            }}
            contentContainerStyle={{
              flexGrow: 1,
            }}
            scrollEnabled={scrollEnabled}
            onLayout={onScrollViewLayout}
            snapToOffsets={
              graphDetailScreenHeaderLayout != null
                ? [graphDetailScreenHeaderLayout.height]
                : undefined
            }
            snapToStart={false}
            onScroll={onScrollViewScroll}
            ref={scrollViewRef}
            scrollEventThrottle={0}
          >
            <GraphDetailScreenHeader
              image={headerImage}
              title={Strings.Shared.SourceBreakDown}
              subTitle={formatInterval(selectedTimeRangeInterval)}
              dsp={dsp}
              onLayout={onGraphDetailScreenHeaderLayout}
            />
            {tableData != null &&
            scrollViewLayout != null &&
            graphData != null ? (
              <View
                style={{
                  height: scrollViewLayout.height,
                }}
              >
                <TrackStreamSourcesTable
                  sections={tableData}
                  sourceColors={sourceColorsByDsp[dsp]}
                />
                <SourceBreakdownAreaGraph
                  data={graphData}
                  sourceOrder={sourceOrder[dsp]}
                  sourceColors={sourceColorsByDsp[dsp]}
                  interval={selectedTimeRangeInterval}
                  selectedDate={selectedDate}
                  setSelectedDate={setSelectedDate}
                  onHoverDate={() => {
                    setScrollEnabled(false)
                  }}
                  onHoverDateEnd={() => {
                    setScrollEnabled(true)
                  }}
                  style={{
                    flex: 1,
                  }}
                />
              </View>
            ) : (
              <CenteredActivityIndicator />
            )}
            <TimeIntervalSelector
              onSelectTimeInterval={onTimeIntervalChange}
              selectedTimeInterval={selectedTimeInterval}
              options={timeIntervals}
            />
          </ScrollView>
          {displayDownArrow && (
            <View
              style={{
                position: 'absolute',
                left: 0,
                bottom: 0,
                right: 0,
                alignItems: 'center',
              }}
              pointerEvents="box-none"
            >
              <TouchableOpacity
                style={{
                  padding: 20,
                }}
                onPress={() => {
                  if (graphDetailScreenHeaderLayout != null) {
                    scrollViewRef.current?.scrollTo({
                      y: graphDetailScreenHeaderLayout.height,
                    })
                  }
                }}
              >
                <ArrowDown />
              </TouchableOpacity>
            </View>
          )}
        </ErrorBoundary>
      </CommonScreenContainer>
    </ScreenContext.Provider>
  )
}
