import { Badge, Box, Flex, Input, Skeleton, Spinner, Stack, Text, Tooltip } from '@chakra-ui/react'
import { Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Info, Trophy, X } from 'lucide-react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useOutletContext } from 'react-router-dom'
import { CalledItAvatar, CalledItButton, CalledItCard } from '../components'
import { fetchBets, fetchMatchBetStats, upsertBet, type BetResponse, type MatchBetStats } from '../features/bets/betApi'
import { useMatches } from '../features/matches/useMatches'
import type { MatchResponse } from '../features/matches/matchApi'
import { teamLogoUrl } from '../features/teams/teamApi'
import { formatDate, formatDateTime } from '../utils/dateFormat'
import type { AppOutletContext } from '../types/auth'

type BetDraft = { homeScore: string; awayScore: string }
type SaveState = 'idle' | 'saving' | 'saved' | 'error'
type ViewMode = 'round' | 'date'

type MatchGroup = { label: string; matches: MatchResponse[] }

const KNOCKOUT_ORDER = ['Round of 32', 'Round of 16', 'Quarter-Finals', 'Semi-Finals', 'Third Place', 'Final']

function roundSortKey(round: string): string {
  const idx = KNOCKOUT_ORDER.indexOf(round)
  if (idx >= 0) return `B_${String(idx).padStart(2, '0')}`
  return `A_${round}`
}

function groupByRound(matches: MatchResponse[]): MatchGroup[] {
  const map = new Map<string, MatchResponse[]>()
  for (const m of matches) {
    if (!map.has(m.round)) map.set(m.round, [])
    map.get(m.round)!.push(m)
  }
  return Array.from(map.entries())
    .map(([round, ms]) => ({
      label: round,
      matches: [...ms].sort((a, b) => a.matchDate.localeCompare(b.matchDate)),
    }))
    .sort((a, b) => roundSortKey(a.label).localeCompare(roundSortKey(b.label)))
}

function groupByDate(matches: MatchResponse[]): MatchGroup[] {
  const map = new Map<string, MatchResponse[]>()
  for (const m of matches) {
    const day = m.matchDate.slice(0, 10)
    if (!map.has(day)) map.set(day, [])
    map.get(day)!.push(m)
  }
  return Array.from(map.entries())
    .sort(([a], [b]) => a.localeCompare(b))
    .map(([day, ms]) => ({
      label: formatDate(day),
      matches: [...ms].sort((a, b) => a.matchDate.localeCompare(b.matchDate)),
    }))
}

function isLocked(matchDate: string): boolean {
  return new Date(matchDate).getTime() - Date.now() < 60 * 1000
}

export function MatchesPage() {
  const { session, selectedCompetitionId } = useOutletContext<AppOutletContext>()
  const competitionId = selectedCompetitionId ? Number(selectedCompetitionId) : null
  const { matches, loading, error } = useMatches(session.token, competitionId)

  const [bets, setBets] = useState<Record<number, BetDraft>>({})
  const betsRef = useRef<Record<number, BetDraft>>({})
  const [betPoints, setBetPoints] = useState<Record<number, number | null>>({})
  const [saveState, setSaveState] = useState<Record<number, SaveState>>({})
  const [saveError, setSaveError] = useState<Record<number, string>>({})
  const [matchStats, setMatchStats] = useState<Record<number, MatchBetStats | null>>({})
  const [statsExpanded, setStatsExpanded] = useState<Record<number, boolean>>({})
  const debounceTimers = useRef<Record<number, ReturnType<typeof setTimeout>>>({})

  const [viewMode, setViewMode] = useState<ViewMode>('round')
  const [roundIndex, setRoundIndex] = useState(0)
  const [betsLoaded, setBetsLoaded] = useState(false)
  const [scoringExpanded, setScoringExpanded] = useState(false)

  const roundGroups = useMemo(() => groupByRound(matches), [matches])
  const dateGroups = useMemo(() => groupByDate(matches), [matches])
  const groups = viewMode === 'round' ? roundGroups : dateGroups

  // Jump to the first group with an upcoming match once groups are available
  useEffect(() => {
    if (roundGroups.length === 0) return
    const now = Date.now()
    const idx = roundGroups.findIndex((r) =>
      r.matches.some((m) => new Date(m.matchDate).getTime() > now),
    )
    setRoundIndex(idx >= 0 ? idx : roundGroups.length - 1)
  }, [roundGroups.length > 0])

  // Load existing bets when competition changes
  useEffect(() => {
    if (!competitionId) return
    setBetsLoaded(false)
    fetchBets(session.token, competitionId)
      .then((existing: BetResponse[]) => {
        const map: Record<number, BetDraft> = {}
        const pointsMap: Record<number, number | null> = {}
        for (const b of existing) {
          map[b.matchId] = { homeScore: String(b.homeScore), awayScore: String(b.awayScore) }
          pointsMap[b.matchId] = b.points
        }
        betsRef.current = map
        setBets(map)
        setBetPoints(pointsMap)
        setBetsLoaded(true)
      })
      .catch(() => setBetsLoaded(true))
  }, [session.token, competitionId])

  function handleBetChange(matchId: number, field: keyof BetDraft, raw: string) {
    const value = raw === '' ? '' : String(Math.max(0, Number(raw)))
    const updated = {
      ...betsRef.current,
      [matchId]: { homeScore: betsRef.current[matchId]?.homeScore ?? '', awayScore: betsRef.current[matchId]?.awayScore ?? '', [field]: value },
    }
    betsRef.current = updated
    setBets(updated)
    setSaveState((s) => ({ ...s, [matchId]: 'idle' }))

    clearTimeout(debounceTimers.current[matchId])
    debounceTimers.current[matchId] = setTimeout(() => void triggerSave(matchId), 800)
  }

  async function triggerSave(matchId: number) {
    const draft = betsRef.current[matchId]
    if (!draft || (draft.homeScore === '' && draft.awayScore === '')) return

    const homeScore = draft.homeScore === '' ? 0 : Number(draft.homeScore)
    const awayScore = draft.awayScore === '' ? 0 : Number(draft.awayScore)

    setSaveState((s) => ({ ...s, [matchId]: 'saving' }))
    try {
      await upsertBet(session.token, matchId, homeScore, awayScore)
      setSaveState((s) => ({ ...s, [matchId]: 'saved' }))
      // Refresh stats silently if the panel is already open
      if (statsExpanded[matchId]) {
        fetchMatchBetStats(session.token, matchId)
          .then((stats) => setMatchStats((prev) => ({ ...prev, [matchId]: stats })))
          .catch(() => {})
      }
    } catch (err: unknown) {
      setSaveState((s) => ({ ...s, [matchId]: 'error' }))
      setSaveError((e) => ({ ...e, [matchId]: err instanceof Error ? err.message : 'Save failed' }))
    }
  }

  function toggleStats(matchId: number) {
    const opening = !statsExpanded[matchId]
    setStatsExpanded((prev) => ({ ...prev, [matchId]: opening }))
    if (opening && !(matchId in matchStats)) {
      setMatchStats((prev) => ({ ...prev, [matchId]: null }))
      fetchMatchBetStats(session.token, matchId)
        .then((stats) => setMatchStats((prev) => ({ ...prev, [matchId]: stats })))
        .catch(() => setMatchStats((prev) => ({ ...prev, [matchId]: null })))
    }
  }

  if (selectedCompetitionId === null) {
    return (
      <CalledItCard cardTitle="Matches">
        <Text color="muted">Select a competition from the header to view its matches.</Text>
      </CalledItCard>
    )
  }

  if (loading || !betsLoaded) {
    return (
      <CalledItCard cardTitle="Matches">
        <Flex justify="center" py="6"><Spinner /></Flex>
      </CalledItCard>
    )
  }

  if (error) {
    return (
      <CalledItCard cardTitle="Matches">
        <Text color="red.500">{error}</Text>
      </CalledItCard>
    )
  }

  if (groups.length === 0) {
    return (
      <CalledItCard cardTitle="Matches">
        <Text color="muted">No matches found for this competition.</Text>
      </CalledItCard>
    )
  }

  const currentGroup = roundGroups[roundIndex]

  return (
    <CalledItCard>
      {/* Scoring info */}
      <Box mb="4" borderWidth="1px" borderColor="border" rounded="xl" overflow="hidden">
        <Flex
          as="button"
          align="center"
          justify="space-between"
          px="4"
          py="2.5"
          w="full"
          cursor="pointer"
          bg="subtle"
          _hover={{ bg: 'bg.muted' }}
          transition="all 0.15s"
          onClick={() => setScoringExpanded((v) => !v)}
        >
          <Flex align="center" gap="2">
            <Info size={14} color="var(--chakra-colors-brand-500, #3b82f6)" />
            <Text fontSize="xs" fontWeight="600" color="heading">How points work</Text>
          </Flex>
          {scoringExpanded ? <ChevronUp size={13} /> : <ChevronDown size={13} />}
        </Flex>

        {scoringExpanded && (
          <Box px="4" py="3" bg="bg" borderTopWidth="1px" borderColor="border">
            <Stack gap="1.5">
              <Flex justify="space-between" align="center">
                <Text fontSize="xs" color="text">Exact score (e.g. you pick 2–1, result is 2–1)</Text>
                <Badge colorPalette="green" variant="subtle" fontSize="xs" px="2" flexShrink={0}>+5 pts</Badge>
              </Flex>
              <Flex justify="space-between" align="center">
                <Text fontSize="xs" color="text">Correct outcome (win / draw / loss)</Text>
                <Badge colorPalette="brand" variant="subtle" fontSize="xs" px="2" flexShrink={0}>+2 pts</Badge>
              </Flex>
              <Flex justify="space-between" align="center">
                <Text fontSize="xs" color="text">Correct goal difference (same margin)</Text>
                <Badge colorPalette="brand" variant="subtle" fontSize="xs" px="2" flexShrink={0}>+2 pts</Badge>
              </Flex>
              <Flex justify="space-between" align="center">
                <Text fontSize="xs" color="text">Correct home or away score individually</Text>
                <Badge colorPalette="gray" variant="subtle" fontSize="xs" px="2" flexShrink={0}>+1 pt each</Badge>
              </Flex>
              <Box borderTopWidth="1px" borderColor="border" pt="2" mt="0.5">
                <Flex justify="space-between" align="center">
                  <Text fontSize="xs" color="muted">Maximum per match</Text>
                  <Badge colorPalette="green" variant="solid" fontSize="xs" px="2" flexShrink={0}>11 pts</Badge>
                </Flex>
              </Box>
            </Stack>
          </Box>
        )}
      </Box>

      {/* View mode toggle */}
      <Flex justify="center" mb="4" gap="1" p="1" bg="subtle" rounded="lg" w="fit-content" mx="auto">
        <CalledItButton
          size="sm"
          variant={viewMode === 'round' ? 'solid' : 'ghost'}
          onClick={() => setViewMode('round')}
          px="4"
        >
          By Round
        </CalledItButton>
        <CalledItButton
          size="sm"
          variant={viewMode === 'date' ? 'solid' : 'ghost'}
          onClick={() => setViewMode('date')}
          px="4"
        >
          By Date
        </CalledItButton>
      </Flex>

      {/* Round navigation (round view only) */}
      {viewMode === 'round' && (
        <Flex align="center" justify="space-between" mb="5">
          <CalledItButton
            variant="ghost"
            size="sm"
            disabled={roundIndex === 0}
            onClick={() => setRoundIndex((i) => i - 1)}
            px="2"
          >
            <ChevronLeft size={18} />
          </CalledItButton>

          <Stack gap="0" align="center">
            <Text fontWeight="700" color="heading" fontSize="lg">
              {currentGroup.label}
            </Text>
            <Text fontSize="xs" color="muted">
              {roundIndex + 1} / {roundGroups.length}
            </Text>
          </Stack>

          <CalledItButton
            variant="ghost"
            size="sm"
            disabled={roundIndex === roundGroups.length - 1}
            onClick={() => setRoundIndex((i) => i + 1)}
            px="2"
          >
            <ChevronRight size={18} />
          </CalledItButton>
        </Flex>
      )}

      {/* Matches — round view: single group; date view: all groups */}
      {viewMode === 'round' ? (
        <Stack gap="3">
          {currentGroup.matches.map((match) => renderMatch(match))}
        </Stack>
      ) : (
        <Stack gap="6">
          {dateGroups.map((group) => (
            <Stack key={group.label} gap="3">
              <Text fontWeight="700" color="heading" fontSize="sm" borderBottomWidth="1px" borderColor="border" pb="1">
                {group.label}
              </Text>
              {group.matches.map((match) => renderMatch(match))}
            </Stack>
          ))}
        </Stack>
      )}
    </CalledItCard>
  )

  function renderMatch(match: MatchResponse) {
    const locked = isLocked(match.matchDate)
    const draft = bets[match.id]
    const state = saveState[match.id] ?? 'idle'
    const hasResult = match.homeScore !== null

    return (
      <Stack
        key={match.id}
        gap="1"
        px="4"
        py="3"
        borderWidth="1px"
        borderColor="border"
        rounded="xl"
        bg="subtle"
        opacity={locked && !draft ? 0.6 : 1}
      >
        <Text fontSize="xs" color="muted" textAlign="center">
          {formatDateTime(match.matchDate)}
        </Text>

        <Flex align="center" justify="center" gap="3">
          <Flex align="center" gap="2" flex="1" justify="flex-end" minW="0">
            <Text fontWeight="600" color="heading" textAlign="right" truncate>
              {match.homeTeam.name}
            </Text>
            <CalledItAvatar src={teamLogoUrl(match.homeTeam.logoUrl)} name={match.homeTeam.name} size="xs" flexShrink={0} />
          </Flex>

          {locked ? (
            <Text fontSize="sm" color="muted" fontWeight="600" minW="16" textAlign="center">
              {draft ? `${draft.homeScore} – ${draft.awayScore}` : '– –'}
            </Text>
          ) : (
            <Flex align="center" gap="1">
              <Input
                type="number"
                min={0}
                placeholder="0"
                value={draft?.homeScore ?? ''}
                onChange={(e) => handleBetChange(match.id, 'homeScore', e.target.value)}
                w="14"
                textAlign="center"
                borderColor="border"
                bg="white"
                size="sm"
              />
              <Text color="muted" fontWeight="600" px="1">–</Text>
              <Input
                type="number"
                min={0}
                placeholder="0"
                value={draft?.awayScore ?? ''}
                onChange={(e) => handleBetChange(match.id, 'awayScore', e.target.value)}
                w="14"
                textAlign="center"
                borderColor="border"
                bg="white"
                size="sm"
              />
            </Flex>
          )}

          <Flex align="center" gap="2" flex="1" justify="flex-start" minW="0">
            <CalledItAvatar src={teamLogoUrl(match.awayTeam.logoUrl)} name={match.awayTeam.name} size="xs" flexShrink={0} />
            <Text fontWeight="600" color="heading" textAlign="left" truncate>
              {match.awayTeam.name}
            </Text>
          </Flex>

          <Flex align="center" gap="2" flexShrink={0} minW="6" justify="flex-end">
            {!locked && (
              <>
                {state === 'saving' && <Spinner size="xs" color="muted" />}
                {state === 'saved' && <Check size={14} color="var(--chakra-colors-green-500)" />}
                {state === 'error' && (
                  <Tooltip.Root openDelay={200} closeDelay={0}>
                    <Tooltip.Trigger asChild>
                      <span style={{ display: 'flex' }}>
                        <X size={14} color="var(--chakra-colors-red-500)" />
                      </span>
                    </Tooltip.Trigger>
                    <Tooltip.Positioner>
                      <Tooltip.Content>
                        <Tooltip.Arrow><Tooltip.ArrowTip /></Tooltip.Arrow>
                        {saveError[match.id]}
                      </Tooltip.Content>
                    </Tooltip.Positioner>
                  </Tooltip.Root>
                )}
              </>
            )}
            {hasResult && (
              <Badge colorPalette="brand" variant="subtle" px="2" fontSize="xs" flexShrink={0}>
                {match.homeScore}–{match.awayScore}
              </Badge>
            )}
            {hasResult && betPoints[match.id] != null && (
              <Badge colorPalette="green" variant="subtle" px="2" fontSize="xs" flexShrink={0}>
                {betPoints[match.id]} pts
              </Badge>
            )}
          </Flex>
        </Flex>

        {/* toggle button */}
        <Flex
          as="button"
          align="center"
          justify="center"
          gap="1"
          mt="1"
          py="1"
          w="full"
          rounded="lg"
          color="muted"
          cursor="pointer"
          _hover={{ color: 'heading', bg: 'bg.muted' }}
          transition="all 0.15s"
          onClick={() => toggleStats(match.id)}
        >
          <Text fontSize="xs">What others said</Text>
          {statsExpanded[match.id] ? <ChevronUp size={12} /> : <ChevronDown size={12} />}
        </Flex>

        {statsExpanded[match.id] && renderBetStats(match.id)}
      </Stack>
    )
  }

  function renderBetStats(matchId: number) {
    const stats = matchStats[matchId]
    const isLoading = matchId in matchStats && stats === null

    if (isLoading) {
      return <Skeleton height="70px" rounded="lg" mt="1" />
    }

    if (!stats || stats.totalBets === 0) return null

    const bars: { label: string; pct: number; color: string }[] = [
      { label: 'Home Win', pct: stats.homeWinPercent, color: 'var(--chakra-colors-brand-500, #3b82f6)' },
      { label: 'Draw', pct: stats.drawPercent, color: 'var(--chakra-colors-gray-400, #9ca3af)' },
      { label: 'Away Win', pct: stats.awayWinPercent, color: 'var(--chakra-colors-orange-400, #fb923c)' },
    ]

    return (
      <Box
        mt="2"
        px="3"
        py="2.5"
        bg="bg.subtle"
        borderWidth="1px"
        borderColor="border"
        rounded="xl"
        position="relative"
        style={{ animation: 'calledit-bubble-in 320ms cubic-bezier(0.34,1.56,0.64,1) both' }}
      >
        {/* bubble pointer */}
        <Box
          position="absolute"
          top="-6px"
          left="50%"
          transform="translateX(-50%)"
          w="0"
          h="0"
          style={{
            borderLeft: '6px solid transparent',
            borderRight: '6px solid transparent',
            borderBottom: '6px solid var(--chakra-colors-border)',
          }}
        />
        <Box
          position="absolute"
          top="-4px"
          left="50%"
          transform="translateX(-50%)"
          w="0"
          h="0"
          style={{
            borderLeft: '5px solid transparent',
            borderRight: '5px solid transparent',
            borderBottom: '5px solid var(--chakra-colors-bg-subtle)',
          }}
        />

        <Flex align="center" justify="space-between" mb="2">
          <Text fontSize="xs" fontWeight="700" color="heading" letterSpacing="wide">
            What others said
          </Text>
          <Text fontSize="xs" color="muted">
            {stats.totalBets} prediction{stats.totalBets !== 1 ? 's' : ''}
          </Text>
        </Flex>

        {/* outcome bars */}
        <Stack gap="1" mb="2.5">
          {bars.map(({ label, pct, color }) => (
            <Flex key={label} align="center" gap="2">
              <Box w="16" flexShrink={0}>
                <Text fontSize="2xs" color="muted" textAlign="right">{label}</Text>
              </Box>
              <Box flex="1" h="5px" bg="bg.muted" rounded="full" overflow="hidden">
                <Box
                  h="full"
                  rounded="full"
                  style={{
                    width: `${pct}%`,
                    backgroundColor: color,
                    transition: 'width 600ms ease',
                  }}
                />
              </Box>
              <Text fontSize="2xs" color="heading" fontWeight="600" w="8" flexShrink={0}>
                {pct}%
              </Text>
            </Flex>
          ))}
        </Stack>

        {/* avg + top pick */}
        <Flex align="center" justify="space-between" gap="2" flexWrap="wrap">
          <Flex align="center" gap="1.5">
            <Text fontSize="xs" color="muted">Avg:</Text>
            <Badge variant="subtle" colorPalette="gray" fontSize="xs" px="2" fontVariantNumeric="tabular-nums">
              {stats.avgHomeScore.toFixed(1)} – {stats.avgAwayScore.toFixed(1)}
            </Badge>
          </Flex>
          <Flex align="center" gap="1.5">
            <Trophy size={11} color="var(--chakra-colors-yellow-500, #eab308)" />
            <Text fontSize="xs" color="muted">Most tipped:</Text>
            <Badge variant="subtle" colorPalette="yellow" fontSize="xs" px="2" fontVariantNumeric="tabular-nums">
              {stats.topPickHomeScore}–{stats.topPickAwayScore}
            </Badge>
            <Text fontSize="xs" color="muted">({stats.topPickCount}×)</Text>
          </Flex>
        </Flex>
      </Box>
    )
  }
}
