import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {ListRenderItem, View} from 'react-native';
import {useTranslation} from 'react-i18next';
import {NavigationProp, useNavigation} from '@react-navigation/native';
import {Chips, StateCard, Typography} from '@components';
import {List} from '@components/_HuGo/List';
import {useModalHandler} from '@hooks/useModalHandler';
import {ProdeV2StackParamList} from '@modules/prodev2/navigation/interfaces';
import {useMatches} from '@modules/prodev2/hooks/useMatches';
import {
  CompetitionDetail,
  Match,
  MatchFilter,
  MatchInPrediction,
  MatchWithPrediction,
  Prediction,
} from '@modules/prodev2/interfaces';
import {
  getNextToPredict,
  getTournamentPrediction,
  getUserRanking,
} from '@modules/prodev2/services';
import {MAX_STAGGER_INDEX, prodeKeys} from '@modules/prodev2/constants';
import {getMatchDateHeader, getMatchLocalDateKey} from '@modules/prodev2/utils';
import {Screens} from '@shared/constants';
import {useQuery} from '@shared/hooks/queries-v5/useQuery';
import {useTheme} from '@shared/theme';
import {format, parseISO} from '@shared/utils/datetime';

import {AnimatedCardEntrance} from '../AnimatedCardEntrance';
import {MatchesListSkeleton, PredictionsTabSkeleton} from '../CardSkeletons';
import {MatchCard} from '../MatchCard';
import {PendingPredictionCard} from '../PendingPredictionCard';
import {PodiumCard} from '../PodiumCard';
import {PodiumPreviewContent} from '../PodiumPreviewContent';
import {ProdeHeroCard} from '../ProdeHeroCard';
import {MatchDetailModal} from '../MatchDetailModal';
import {PredictionModal} from '../PredictionModal';
import {styles} from './styles';

export interface Props {
  competitionId: number;
  competition: CompetitionDetail;
  onNavigateToRanking?: () => void;
}

const FILTER_CHIP_KEYS: {key: MatchFilter; labelKey: string}[] = [
  {key: MatchFilter.Upcoming, labelKey: 'sportsPool.matches.to_play'},
  {
    key: MatchFilter.NeedsPrediction,
    labelKey: 'sportsPool.matches.prediction_pending',
  },
  {key: MatchFilter.Finished, labelKey: 'sportsPool.matches.finished'},
];

type MatchListRow =
  | {kind: 'date'; key: string; dateDisplay: string}
  | {kind: 'match'; key: string; mwp: MatchWithPrediction};

const keyExtractor = (item: MatchListRow) => item.key;

export function PredictionsTab({
  competitionId,
  competition,
  onNavigateToRanking,
}: Props) {
  const {t} = useTranslation();
  const {theme} = useTheme();
  const navigation = useNavigation<NavigationProp<ProdeV2StackParamList>>();
  const [isPredictionModalOpen, setIsPredictionModalOpen] = useState(false);
  const [isMatchDetailModalOpen, setIsMatchDetailModalOpen] = useState(false);
  const [activeMatch, setActiveMatch] =
    useState<Nullable<Match | MatchInPrediction>>(null);
  const [activePrediction, setActivePrediction] =
    useState<Nullable<Prediction>>(null);
  const [activeFilter, setActiveFilter] = useState<MatchFilter>(
    MatchFilter.Upcoming,
  );

  const {data: userRanking, isLoading: isRankingLoading} = useQuery(
    prodeKeys.userRanking(competitionId),
    () => getUserRanking(competitionId),
    {enabled: !!competitionId},
  );
  const {data: tournamentPrediction, isLoading: isTournamentPredictionLoading} =
    useQuery(
      prodeKeys.tournamentPrediction(competitionId),
      () => getTournamentPrediction(competitionId),
      {enabled: !!competitionId},
    );
  const {data: nextToPredict} = useQuery(
    prodeKeys.nextToPredict(competitionId),
    () => getNextToPredict(competitionId),
    {enabled: !!competitionId},
  );

  const matchesParams = useMemo(
    () =>
      activeFilter === MatchFilter.All ? undefined : {filter: activeFilter},
    [activeFilter],
  );

  const {
    data: matches,
    getNextPage,
    hasNextPage,
    isFetchingNextPage,
    isLoading: isMatchesLoading,
  } = useMatches(competitionId, matchesParams);

  const matchRows = useMemo<MatchListRow[]>(() => {
    const grouped = new Map<string, MatchWithPrediction[]>();
    matches.forEach(mwp => {
      const date = getMatchLocalDateKey(
        mwp.match.matchDate,
        mwp.match.matchTime,
      );
      if (!grouped.has(date)) {
        grouped.set(date, []);
      }
      grouped.get(date)!.push(mwp);
    });

    return Array.from(grouped.entries())
      .sort(([a], [b]) => a.localeCompare(b))
      .flatMap(([date, dateMatches]) => {
        const rows: MatchListRow[] = [
          {
            kind: 'date',
            key: `date-${date}`,
            dateDisplay: getMatchDateHeader(date),
          },
        ];
        dateMatches.forEach(mwp => {
          rows.push({kind: 'match', key: `match-${mwp.match.id}`, mwp});
        });
        return rows;
      });
  }, [matches]);

  const hasTournamentStarted =
    competition.status === 'ACTIVE' || competition.status === 'FINISHED';
  const canModifyPodium = !!tournamentPrediction?.isOpen;
  const showPendingCard =
    !!nextToPredict?.match &&
    nextToPredict.isPredictionOpen &&
    !!nextToPredict.match.homeTeam &&
    !!nextToPredict.match.awayTeam;

  // Header occupies these stagger slots: hero (+ optional podium/pending) cards,
  // then the "Matches" title and the filter chips. List rows continue from here.
  const visibleCardCount =
    1 +
    (canModifyPodium && tournamentPrediction ? 1 : 0) +
    (showPendingCard ? 1 : 0);
  const headerItemCount = visibleCardCount + 2;

  const {
    isVisible: isPodiumPreviewOpen,
    onOpenModal: onOpenPodiumPreview,
    onCloseModal: onClosePodiumPreview,
  } = useModalHandler();

  const podiumDeadline = useMemo(() => {
    if (!competition.startDate) return undefined;
    try {
      return format(parseISO(competition.startDate), 'd/M HH:mm');
    } catch {
      return undefined;
    }
  }, [competition.startDate]);

  const onModifyPodium = useCallback(() => {
    onClosePodiumPreview();
    navigation.navigate(Screens.PRODEV2_PODIUM, {competitionId});
  }, [competitionId, navigation, onClosePodiumPreview]);

  const onPodiumPress = useCallback(() => {
    if (tournamentPrediction?.prediction) {
      onOpenPodiumPreview();
    } else {
      navigation.navigate(Screens.PRODEV2_PODIUM, {competitionId});
    }
  }, [
    competitionId,
    navigation,
    onOpenPodiumPreview,
    tournamentPrediction?.prediction,
  ]);

  const onMatchPress = useCallback(
    (matchId: number) => {
      const foundMwp = matches.find(mwp => mwp.match.id === matchId);
      if (!foundMwp) return;

      const matchData = foundMwp.match;
      setActiveMatch(matchData);
      setActivePrediction(foundMwp.prediction);

      const hasDefinedTeams =
        matchData.homeTeam !== null && matchData.awayTeam !== null;

      if (matchData.status === 'FINISHED') {
        setIsMatchDetailModalOpen(true);
      } else if (foundMwp.isPredictionOpen && hasDefinedTeams) {
        setIsPredictionModalOpen(true);
      } else {
        setIsMatchDetailModalOpen(true);
      }
    },
    [matches],
  );

  const onPendingPredictionPress = () => {
    if (!nextToPredict?.match) return;
    setActiveMatch(nextToPredict.match);
    setActivePrediction(nextToPredict.prediction);
    setIsPredictionModalOpen(true);
  };

  const onClosePredictionModal = () => {
    setIsPredictionModalOpen(false);
    setActiveMatch(null);
    setActivePrediction(null);
  };

  const onCloseMatchDetailModal = () => {
    setIsMatchDetailModalOpen(false);
    setActiveMatch(null);
    setActivePrediction(null);
  };

  // A filter chip only shows when matches in that state can actually exist,
  // derived from data already on screen (no extra queries):
  // - Upcoming: until the tournament is over
  // - NeedsPrediction: only while there's something pending to predict
  // - Finished: only once the tournament has started
  const isTournamentFinished = competition.status === 'FINISHED';
  const hasPendingPrediction =
    !!nextToPredict?.match && nextToPredict.isPredictionOpen;

  const visibleFilterChips = useMemo(
    () =>
      FILTER_CHIP_KEYS.filter(({key}) => {
        switch (key) {
          case MatchFilter.Upcoming:
            return !isTournamentFinished;
          case MatchFilter.NeedsPrediction:
            return hasPendingPrediction;
          case MatchFilter.Finished:
            return hasTournamentStarted;
          default:
            return true;
        }
      }),
    [isTournamentFinished, hasPendingPrediction, hasTournamentStarted],
  );

  // If the active filter's chip is no longer visible, fall back to All.
  useEffect(() => {
    if (
      activeFilter !== MatchFilter.All &&
      !visibleFilterChips.some(c => c.key === activeFilter)
    ) {
      setActiveFilter(MatchFilter.All);
    }
  }, [activeFilter, visibleFilterChips]);

  const filterChips = useMemo(
    () =>
      visibleFilterChips.map((chip, index) => ({
        id: index,
        label: t(chip.labelKey),
      })),
    [t, visibleFilterChips],
  );
  const selectedChipId = visibleFilterChips.findIndex(
    c => c.key === activeFilter,
  );

  const onFilterPress = (id: number) => {
    const filter = visibleFilterChips[id].key;
    setActiveFilter(prev => (prev === filter ? MatchFilter.All : filter));
  };

  const renderItem: ListRenderItem<MatchListRow> = useCallback(
    ({item, index}) => {
      // Delay continues the header's sequence; the cap uses the row's own index
      // so several rows cascade (not just the 1-2 that fit under the header).
      const staggerIndex = headerItemCount + index;
      if (item.kind === 'date') {
        return (
          <AnimatedCardEntrance
            index={staggerIndex}
            disabled={index > MAX_STAGGER_INDEX}
            style={styles.dateRow}>
            <Typography
              variant="xs"
              weight="semiBold"
              color={theme.text.neutral.lighter}>
              {item.dateDisplay}
            </Typography>
          </AnimatedCardEntrance>
        );
      }
      return (
        <AnimatedCardEntrance
          index={staggerIndex}
          disabled={index > MAX_STAGGER_INDEX}
          style={styles.matchRow}>
          <MatchCard
            match={item.mwp.match}
            prediction={item.mwp.prediction}
            isPredictionOpen={item.mwp.isPredictionOpen}
            onPress={() => onMatchPress(item.mwp.match.id)}
          />
        </AnimatedCardEntrance>
      );
    },
    [headerItemCount, onMatchPress, theme.text.neutral.lighter],
  );

  const listHeader = (
    <View style={styles.header}>
      <View style={styles.infoCardsContainer}>
        <AnimatedCardEntrance index={0}>
          <ProdeHeroCard
            startDate={competition.startDate}
            ranking={userRanking}
            hasTournamentStarted={hasTournamentStarted}
            onPress={onNavigateToRanking}
          />
        </AnimatedCardEntrance>
        {canModifyPodium && tournamentPrediction && (
          <AnimatedCardEntrance index={1}>
            <PodiumCard
              tournamentPrediction={tournamentPrediction}
              deadline={podiumDeadline}
              onPress={onPodiumPress}
            />
          </AnimatedCardEntrance>
        )}
        {showPendingCard && (
          <AnimatedCardEntrance
            index={canModifyPodium && tournamentPrediction ? 2 : 1}>
            <PendingPredictionCard
              match={nextToPredict!.match}
              onPress={onPendingPredictionPress}
            />
          </AnimatedCardEntrance>
        )}
      </View>
      <AnimatedCardEntrance index={visibleCardCount}>
        <Typography variant="m" weight="semiBold">
          {t('sportsPool.matches.title')}
        </Typography>
      </AnimatedCardEntrance>
      <AnimatedCardEntrance index={visibleCardCount + 1}>
        <Chips
          list={filterChips}
          selected={selectedChipId === -1 ? undefined : selectedChipId}
          onItemPress={onFilterPress}
          showActiveIcon={false}
        />
      </AnimatedCardEntrance>
    </View>
  );

  const isInitialLoading = isRankingLoading || isTournamentPredictionLoading;

  return isInitialLoading ? (
    <PredictionsTabSkeleton />
  ) : (
    <>
      <View
        style={[
          styles.container,
          {backgroundColor: theme.background.layout.default},
        ]}>
        <List<MatchListRow>
          data={matchRows}
          keyExtractor={keyExtractor}
          renderItem={renderItem}
          isLoading={isMatchesLoading}
          isFetchingNextPage={isFetchingNextPage}
          hasNextPage={hasNextPage}
          onNextPage={getNextPage}
          withRefresh={false}
          ListHeaderComponent={listHeader}
          ListEmptyComponent={
            isMatchesLoading ? (
              <MatchesListSkeleton />
            ) : (
              <View style={styles.emptyStateWrapper}>
                <StateCard
                  variant="empty"
                  title={t('sportsPool.matches.empty')}
                />
              </View>
            )
          }
          backgroundColor={theme.background.layout.default}
          style={styles.listContent}
        />
      </View>
      <PredictionModal
        isOpen={isPredictionModalOpen}
        onClose={onClosePredictionModal}
        competitionId={competitionId}
        match={activeMatch}
        existingPrediction={activePrediction}
      />
      <MatchDetailModal
        isOpen={isMatchDetailModalOpen}
        onClose={onCloseMatchDetailModal}
        competitionId={competitionId}
        match={activeMatch}
        prediction={activePrediction}
      />
      <PodiumPreviewContent
        isVisible={isPodiumPreviewOpen}
        onClose={onClosePodiumPreview}
        prediction={tournamentPrediction?.prediction ?? null}
        deadline={podiumDeadline}
        isOpen={canModifyPodium}
        onModify={onModifyPodium}
      />
    </>
  );
}

export default PredictionsTab;
