import { eachDayOfInterval } from 'date-fns/esm'
import _ from 'lodash'
import React, { useCallback, useContext, useRef, useState } from 'react'
import {
  StyleProp,
  Text,
  View,
  ViewStyle,
  ScrollView,
  TouchableOpacity,
} from 'react-native'
import { colorsV2, sourceColors } from '../Colors'
import { AutoLayout } from '../components/AutoLayout'
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 { ProductImage } from '../components/ProductImage'
import { SourceBreakdownAreaGraph } from '../components/SourceBreakdownAreaGraph'
import { TimeIntervalSelector } from '../components/TimeIntervalSelector'
import { ScreenNames } from '../constants/ScreenNames'
import { SourceLean, sourceLeans, sourceOrder, StreamingDsp } from '../Consts'
import { ArtistContext } from '../contexts/ArtistContext'
import { ScreenContext } from '../contexts/ScreenContext'
import { DateData } from '../DateData'
import { EventType, sendAnalyticsEvent } from '../hooks/useAnalytics'
import { useComponentLayout } from '../hooks/useComponentLayout'
import { useScreenRefreshControl } from '../hooks/useScreenRefreshControl'
import { useTrack } from '../hooks/useTrack'
import { useTrackStreamSources } from '../hooks/useTrackStreamSources'
import { useTrackTopCountryCodes } from '../hooks/useTrackTopCountries'
import { Strings } from '../i18n'
import ArrowDown from '../icons/ArrowDown'
import { ReactNavigationProps } from '../Navigation'
import { TextStylesV2 } from '../Styles'
import { defaultTimeInterval, TimeInterval } from '../types/TimeInterval'
import { DspSource } from '../types/types'
import {
  formatCountShort,
  formatPercent,
  notEmpty,
  timeRangeInterval,
} from '../util'
import { formatDateString, formatInterval } from '../util/date'

const leanTitles = {
  leanForward: Strings.Shared.Forward,
  leanBack: Strings.Shared.Back,
}

function getDspData(
  sources: DateData<Record<StreamingDsp, DspSource | null> | null>[],
  dsp: StreamingDsp
): 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)
}

interface LeanSummary {
  type: SourceLean
  streams: number
  percentage: number
}
interface SourceSummary {
  source: string
  streams: number
  percentage: number
}

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: DateData<Record<StreamingDsp, DspSource | null> | null>[],
  dsp: StreamingDsp,
  interval: Interval
): {
  date: string
  sources: Record<string, number | null>
}[] {
  const sourcesForDsp: DateData<Record<string, number | null>>[] = sources.map(
    ({ date, data }) => ({
      date,
      data:
        data != null
          ? { ...data?.[dsp]?.leanBack, ...data?.[dsp]?.leanForward }
          : {},
    })
  )

  const daysInInterval = eachDayOfInterval(interval)

  return daysInInterval.flatMap((d) => {
    const sources = sourcesForDsp.find(
      (x) => x.date === formatDateString(d)
    )?.data
    return sources != null
      ? [
          {
            date: formatDateString(d),
            sources,
          },
        ]
      : []
  })
}

const TrackStreamsSourcesTableSourceRow: React.FC<{
  source: string
  streams: number
  percentage: number
  style?: StyleProp<ViewStyle>
}> = ({ source, streams, percentage, style }) => (
  <View
    style={[
      style,
      {
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center',
      },
    ]}
  >
    <View
      style={{
        flexDirection: 'row',
        alignItems: 'center',
        flexShrink: 1,
      }}
    >
      {source !== 'total' && (
        <View
          style={{
            width: 12,
            height: 12,
            borderRadius: 12,
            backgroundColor: sourceColors[source] ?? colorsV2.white,
            marginRight: 8,
            alignItems: 'flex-start',
          }}
        />
      )}
      <Text
        style={[TextStylesV2.tiny, { color: colorsV2.white, flexShrink: 1 }]}
        lineBreakMode="tail"
        numberOfLines={2}
      >
        {_.startCase(source)}
      </Text>
    </View>
    <View style={{ flexDirection: 'row' }}>
      <Text
        style={[
          TextStylesV2.tiny,
          {
            color: colorsV2.white,
            marginRight: 8,
            width: 32,
            textAlign: 'right',
          },
        ]}
      >
        {formatPercent(percentage, 0, false)}
      </Text>
      <Text
        style={[
          TextStylesV2.tiny,
          {
            color: colorsV2.white,
            width: 40,
            textAlign: 'right',
          },
        ]}
      >
        {formatCountShort(streams)}
      </Text>
    </View>
  </View>
)

const TrackStreamsSourcesTableLeanColumn: React.FC<{
  lean: LeanSummary
  sources: SourceSummary[]
  style?: StyleProp<ViewStyle>
}> = ({ lean, sources, style }) => (
  <View style={[style, { flexDirection: 'column', flex: 1 }]}>
    <Text
      style={[
        TextStylesV2.body,
        { color: colorsV2.offWhite, paddingBottom: 16 },
      ]}
    >
      {leanTitles[lean.type]}
    </Text>

    <AutoLayout gap={12}>
      {[
        <TrackStreamsSourcesTableSourceRow
          key={'total'}
          source={'total'}
          streams={lean.streams}
          percentage={lean.percentage}
        />,
        ...sources.map(({ source, streams, percentage }) => (
          <TrackStreamsSourcesTableSourceRow
            key={source}
            source={source}
            streams={streams}
            percentage={percentage}
          />
        )),
      ]}
    </AutoLayout>
  </View>
)

const TrackStreamSourcesTable: React.FC<{
  sections: {
    lean: LeanSummary
    sources: SourceSummary[]
  }[]
  style?: StyleProp<ViewStyle>
}> = ({ sections, style }) => (
  <AutoLayout
    style={[
      {
        flexDirection: 'row',
        paddingBottom: 16,
      },
      style,
    ]}
    gap={30}
  >
    {sections.map(({ lean, sources }) => (
      <TrackStreamsSourcesTableLeanColumn
        key={lean.type}
        lean={lean}
        sources={sources}
      />
    ))}
  </AutoLayout>
)

export const TrackStreamSourcesScreen: React.FC<
  ReactNavigationProps<ScreenNames.TrackStreamSources>
> = ({ navigation, route }) => {
  const screenName = 'Track Stream Sources Screen'
  const { isrc, dsp } = route.params
  const artist = useContext(ArtistContext)
  const countryCode = route.params.countryCode ?? 'worldwide'

  const { data, retry, refreshing, showToast, error } = useTrack(isrc, () =>
    sendAnalyticsEvent(EventType.PULL_TO_REFRESH, {
      artist_selected: artist.name,
      page: screenName,
      subject: 'user',
      verb: 'refreshed',
      object: 'track',
    })
  )
  const track = data?.track

  const trackTopMarketCountryCodes = useTrackTopCountryCodes(
    isrc,
    defaultTimeInterval
  )

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

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

  const maxInterval = defaultTimeInterval //Longest interval represented in the graph
  const [selectedTimeInterval, selectTimeInterval] =
    useState<TimeInterval>(maxInterval)
  const [selectedDate, selectDate] = useState<string | null>(null)
  const selectedInterval = timeRangeInterval(selectedTimeInterval)

  const { data: selectedIntervalSourcesData } = useTrackStreamSources(
    isrc,
    countryCode,
    selectedInterval
  )

  const { data: maxIntervalSourcesData } = useTrackStreamSources(
    isrc,
    countryCode,
    timeRangeInterval(maxInterval)
  )
  const maxIntervalSources = maxIntervalSourcesData?.sources

  const sourcesForLeans: { lean: SourceLean; sources: string[] }[] | null =
    maxIntervalSources != null
      ? sourceLeans.map((lean) => ({
          lean,
          sources: _.uniq(
            maxIntervalSources.flatMap(({ data }) =>
              Object.keys(data?.[dsp]?.[lean] ?? {})
            )
          ),
        }))
      : null

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

  const dspData = sources != null ? getDspData(sources, dsp) : null
  const sections =
    dspData != null && sourcesForLeans != null
      ? leanListSections(dspData, sourcesForLeans)
      : null

  const graphData =
    maxIntervalSources != null
      ? getGraphData(maxIntervalSources, dsp, timeRangeInterval(maxInterval))
      : null

  const [onScrollViewLayout, scrollViewLayout] = useComponentLayout()
  const [onGraphDetailScreenHeaderLayout, graphDetailScreenHeaderLayout] =
    useComponentLayout()

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

  return (
    <ScreenContext.Provider value={screenContext}>
      <CommonScreenContainer>
        <HeaderNav
          title={track?.name}
          showToast={showToast}
          navigation={navigation}
          style={{
            zIndex: 10,
          }}
          right={
            <MarketSelectorButton
              countryCode={countryCode}
              onPress={() => {
                navigation.navigate('MarketSelector', {
                  previousScreen: 'TrackStreamSources',
                  topMarketCountryCodes: trackTopMarketCountryCodes,
                })
              }}
            />
          }
        />
        <ErrorBoundary error={error} refreshControl={refreshControl}>
          <ScrollView
            style={{
              paddingHorizontal: 16,
            }}
            scrollEnabled={scrollEnabled}
            onLayout={onScrollViewLayout}
            snapToOffsets={
              graphDetailScreenHeaderLayout != null
                ? [graphDetailScreenHeaderLayout.height]
                : undefined
            }
            snapToStart={false}
            onScroll={onScrollViewScroll}
            ref={scrollViewRef}
          >
            <GraphDetailScreenHeader
              image={
                track != null ? (
                  <ProductImage
                    product={track.product}
                    isExplicit={false}
                    size={48}
                  />
                ) : null
              }
              title={Strings.Shared.SourceBreakDown}
              subTitle={formatInterval(selectedInterval)}
              dsp={dsp}
              onLayout={onGraphDetailScreenHeaderLayout}
            />
            {sections != null && scrollViewLayout != null && (
              <View
                style={{
                  height: scrollViewLayout.height,
                }}
              >
                <TrackStreamSourcesTable sections={sections} />
                {graphData != null ? (
                  <SourceBreakdownAreaGraph
                    data={graphData}
                    sourceOrder={sourceOrder[dsp]}
                    colors={sourceColors}
                    maxInterval={timeRangeInterval(maxInterval)}
                    selectedInterval={selectedInterval}
                    selectDate={selectDate}
                    selectedDate={selectedDate}
                    onHoverDate={() => {
                      setScrollEnabled(false)
                    }}
                    onHoverDateEnd={() => {
                      setScrollEnabled(true)
                    }}
                    style={{
                      flex: 1,
                    }}
                  />
                ) : null}
              </View>
            )}
            <TimeIntervalSelector
              onSelectTimeInterval={selectTimeInterval}
              selectedTimeInterval={selectedTimeInterval}
            />
          </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>
  )
}
