import { useEffect, useMemo, useRef, useState } from 'react'
import { NavLink, Route, Routes, useLocation } from 'react-router-dom'
import './App.css'
import { loadMatchResults, saveMatchHistory } from './lib/api'
import {
  buildManualGroups,
  convertDateToKey,
  convertDbRecordToGroups,
  formatDateInputValue,
  getServingPlayer,
  getTeamDisplayName,
  getWinnerName,
  parseRoster,
  swapCourtPositions,
} from './lib/match'
import { HistoryPage } from './pages/HistoryPage'
import { ScoreboardPage } from './pages/ScoreboardPage'
import { TeamSelectionPage } from './pages/TeamSelectionPage'
import type {
  ActiveMatchup,
  GroupTeam,
  HistoryUploadPayload,
  LoadStatus,
  MatchHistoryItem,
  PointHistoryEntry,
  RoundGroup,
  ScoreSide,
  ScoreSnapshot,
  ScoreState,
} from './types'

const STORAGE_KEYS = {
  areaA: 'badminton-scoreboard::area-a',
  areaB: 'badminton-scoreboard::area-b',
  history: 'badminton-scoreboard::history',
  targetDate: 'badminton-scoreboard::target-date',
} as const

const defaultAreaA = ['柏威', '建喵', 'Yuki', '阿釧']
const defaultAreaB = ['RURU', '玟瑄', '培根', 'Tim']

const initialScoreState: ScoreState = {
  scoreLeft: 0,
  scoreRight: 0,
  gamesLeft: 0,
  gamesRight: 0,
  currentGame: 1,
  targetScore: 21,
  serving: null,
  leftRightCourtPlayer: 'playerA',
  rightRightCourtPlayer: 'playerA',
}

type SettlementState = {
  error: string
  open: boolean
  uploading: boolean
}

type StreakAnnouncement = {
  count: number
  key: number
  teamName: string
  title: string
}

type VictoryAnnouncement = {
  key: number
  scoreLabel: string
  teamName: string
  title: string
}

const STREAK_TITLES: Record<number, string> = {
  3: '大殺特殺',
  4: '暴走',
  5: '無人能擋',
  6: '主宰比賽',
  7: '像神一般的',
  8: '成為傳說',
}
const PWA_UPDATE_EVENT = 'badminton-scoreboard:pwa-update-ready'
const APP_VERSION_POLL_MS = 30000

function App() {
  const location = useLocation()
  const isScoreboardRoute = location.pathname === '/scoreboard'

  const [targetDate, setTargetDate] = useState(() =>
    loadStoredText(STORAGE_KEYS.targetDate, formatDateInputValue()),
  )
  const [areaAInput, setAreaAInput] = useState(() =>
    loadStoredText(STORAGE_KEYS.areaA, defaultAreaA.join('\n')),
  )
  const [areaBInput, setAreaBInput] = useState(() =>
    loadStoredText(STORAGE_KEYS.areaB, defaultAreaB.join('\n')),
  )
  const [groups, setGroups] = useState<RoundGroup[]>([])
  const [groupSource, setGroupSource] = useState<'idle' | 'db' | 'manual'>('idle')
  const [loadStatus, setLoadStatus] = useState<LoadStatus>('idle')
  const [loadMessage, setLoadMessage] = useState('')
  const [selectedGroupId, setSelectedGroupId] = useState<number | null>(null)
  const [activeMatchup, setActiveMatchup] = useState<ActiveMatchup>({
    leftTeam: null,
    rightTeam: null,
  })
  const [scoreState, setScoreState] = useState<ScoreState>(initialScoreState)
  const [scoreHistory, setScoreHistory] = useState<ScoreSnapshot[]>([])
  const [pointLog, setPointLog] = useState<PointHistoryEntry[]>([])
  const [history, setHistory] = useState<MatchHistoryItem[]>(() =>
    loadStoredHistory(STORAGE_KEYS.history),
  )
  const [settlement, setSettlement] = useState<SettlementState>({
    error: '',
    open: false,
    uploading: false,
  })
  const [streakAnnouncement, setStreakAnnouncement] = useState<StreakAnnouncement | null>(null)
  const [victoryAnnouncement, setVictoryAnnouncement] = useState<VictoryAnnouncement | null>(null)
  const [pwaUpdateReady, setPwaUpdateReady] = useState(false)
  const currentAppVersionRef = useRef<string | null>(null)

  const parsedAreaA = useMemo(() => parseRoster(areaAInput), [areaAInput])
  const parsedAreaB = useMemo(() => parseRoster(areaBInput), [areaBInput])
  const selectedGroup = groups.find((group) => group.id === selectedGroupId) ?? null
  const leftTeam = activeMatchup.leftTeam
  const rightTeam = activeMatchup.rightTeam

  useEffect(() => {
    window.localStorage.setItem(STORAGE_KEYS.targetDate, targetDate)
  }, [targetDate])

  useEffect(() => {
    window.localStorage.setItem(STORAGE_KEYS.areaA, areaAInput)
  }, [areaAInput])

  useEffect(() => {
    window.localStorage.setItem(STORAGE_KEYS.areaB, areaBInput)
  }, [areaBInput])

  useEffect(() => {
    window.localStorage.setItem(STORAGE_KEYS.history, JSON.stringify(history))
  }, [history])

  useEffect(() => {
    if (loadStatus !== 'loaded' || !loadMessage) {
      return
    }

    const timer = window.setTimeout(() => {
      setLoadMessage('')
    }, 1000)

    return () => window.clearTimeout(timer)
  }, [loadMessage, loadStatus])

  useEffect(() => {
    if (!streakAnnouncement) {
      return
    }

    const timer = window.setTimeout(() => {
      setStreakAnnouncement(null)
    }, 1800)

    return () => window.clearTimeout(timer)
  }, [streakAnnouncement])

  useEffect(() => {
    if (!victoryAnnouncement) {
      return
    }

    const timer = window.setTimeout(() => {
      setVictoryAnnouncement(null)
    }, 2200)

    return () => window.clearTimeout(timer)
  }, [victoryAnnouncement])

  useEffect(() => {
    const handlePwaUpdateReady = () => {
      setPwaUpdateReady(true)
    }

    window.addEventListener(PWA_UPDATE_EVENT, handlePwaUpdateReady)

    return () => {
      window.removeEventListener(PWA_UPDATE_EVENT, handlePwaUpdateReady)
    }
  }, [])

  useEffect(() => {
    let active = true

    const checkAppVersion = async () => {
      try {
        const response = await fetch('/api/version', {
          cache: 'no-store',
          headers: {
            'cache-control': 'no-cache',
          },
        })

        if (!response.ok) {
          return
        }

        const payload = (await response.json()) as {
          ok?: boolean
          version?: string
        }
        const nextVersion = payload.version?.trim()

        if (!active || !nextVersion) {
          return
        }

        if (!currentAppVersionRef.current) {
          currentAppVersionRef.current = nextVersion
          return
        }

        if (currentAppVersionRef.current !== nextVersion) {
          currentAppVersionRef.current = nextVersion
          setPwaUpdateReady(true)
        }
      } catch {
        // Ignore transient version-check failures and retry on next poll.
      }
    }

    void checkAppVersion()
    const timer = window.setInterval(() => {
      void checkAppVersion()
    }, APP_VERSION_POLL_MS)

    return () => {
      active = false
      window.clearInterval(timer)
    }
  }, [])

  const resetScoring = (nextState: ScoreState = initialScoreState) => {
    setScoreState(nextState)
    setScoreHistory([])
    setPointLog([])
    setStreakAnnouncement(null)
    setVictoryAnnouncement(null)
    setSettlement({
      error: '',
      open: false,
      uploading: false,
    })
  }

  const selectGroup = (groupId: number, nextGroups = groups) => {
    const nextGroup = nextGroups.find((group) => group.id === groupId)
    const firstTeam = nextGroup?.teams[0] ?? null
    const secondTeam = nextGroup?.teams[1] ?? null

    setSelectedGroupId(nextGroup?.id ?? null)
    setActiveMatchup({
      leftTeam: firstTeam,
      rightTeam: secondTeam,
    })
    resetScoring()
  }

  const applyMatchup = (
    leftTeam: GroupTeam,
    rightTeam: GroupTeam,
    targetScore: number,
  ) => {
    setActiveMatchup({
      leftTeam,
      rightTeam,
    })
    resetScoring({
      ...initialScoreState,
      targetScore,
    })
  }

  const refreshForPwaUpdate = () => {
    const registrationPromise = navigator.serviceWorker?.getRegistration
      ? navigator.serviceWorker.getRegistration()
      : Promise.resolve(undefined)

    void registrationPromise.then((registration) => {
      if (registration?.waiting) {
        registration.waiting.postMessage({ type: 'SKIP_WAITING' })
        return
      }

      window.location.reload()
    })
  }

  const loadGroupsFromDb = async () => {
    if (!targetDate) {
      setLoadStatus('error')
      setLoadMessage('請先選擇日期。')
      return
    }

    setLoadStatus('loading')
    setLoadMessage('正在讀取指定日期的分組資料...')

    try {
      const record = await loadMatchResults(convertDateToKey(targetDate))

      if (!record) {
        setGroups([])
        setSelectedGroupId(null)
        setActiveMatchup({ leftTeam: null, rightTeam: null })
        setGroupSource('idle')
        setLoadStatus('empty')
        setLoadMessage('指定日期沒有資料，請改用手動配對。')
        return
      }

      const nextData = convertDbRecordToGroups(record)
      setAreaAInput(nextData.areaA.join('\n'))
      setAreaBInput(nextData.areaB.join('\n'))
      setGroups(nextData.groups)
      setGroupSource('db')
      setLoadStatus('loaded')
      setLoadMessage(`已載入 ${convertDateToKey(targetDate)} 的分組資料。`)
      selectGroup(nextData.groups[0]?.id ?? 1, nextData.groups)
    } catch (error) {
      setGroups([])
      setSelectedGroupId(null)
      setActiveMatchup({ leftTeam: null, rightTeam: null })
      setGroupSource('idle')
      setLoadStatus('error')
      setLoadMessage(error instanceof Error ? error.message : '讀取資料失敗。')
    }
  }

  const generateManualGroups = () => {
    if (parsedAreaA.length === 0 || parsedAreaB.length === 0) {
      setGroups([])
      setSelectedGroupId(null)
      setActiveMatchup({ leftTeam: null, rightTeam: null })
      setGroupSource('idle')
      setLoadStatus('error')
      setLoadMessage('A 區與 B 區至少都要有 1 位成員。')
      return
    }

    const nextGroups = buildManualGroups(parsedAreaA, parsedAreaB)
    setGroups(nextGroups)
    setGroupSource('manual')
    setLoadStatus('loaded')
    setLoadMessage('已產生手動配對結果，請選擇要使用的組別。')
    selectGroup(nextGroups[0]?.id ?? 1, nextGroups)
  }

  const swapMatchupSides = () => {
    if (scoreHistory.length > 0) {
      return
    }

    setActiveMatchup((current) => ({
      leftTeam: current.rightTeam,
      rightTeam: current.leftTeam,
    }))

    setScoreState((current) => ({
      ...current,
      scoreLeft: current.scoreRight,
      scoreRight: current.scoreLeft,
      gamesLeft: current.gamesRight,
      gamesRight: current.gamesLeft,
      serving:
        current.serving === 'left'
          ? 'right'
          : current.serving === 'right'
            ? 'left'
            : null,
      leftRightCourtPlayer: current.rightRightCourtPlayer,
      rightRightCourtPlayer: current.leftRightCourtPlayer,
    }))
  }

  const swapTeamPlayers = (side: ScoreSide) => {
    if (scoreHistory.length > 0) {
      return
    }

    setScoreState((current) => ({
      ...current,
      leftRightCourtPlayer:
        side === 'left'
          ? swapCourtPositions(current.leftRightCourtPlayer)
          : current.leftRightCourtPlayer,
      rightRightCourtPlayer:
        side === 'right'
          ? swapCourtPositions(current.rightRightCourtPlayer)
          : current.rightRightCourtPlayer,
    }))
  }

  const setServing = (side: ScoreSide) => {
    if (scoreHistory.length > 0) {
      return
    }

    setScoreState((current) => ({
      ...current,
      serving: side,
    }))
  }

  const recordPoint = (side: ScoreSide) => {
    if (!leftTeam || !rightTeam || scoreState.serving === null) {
      return
    }

    const starter = getServerHistoryIndex(scoreState, leftTeam, rightTeam)

    if (starter === null) {
      return
    }

    const winner: 0 | 1 = side === 'left' ? 0 : 1
    const previousPoint = pointLog.at(-1)
    const winCount = previousPoint?.winner === winner ? previousPoint.winCount + 1 : 0
    const streakCount = winCount + 1
    const streakTitle = STREAK_TITLES[streakCount]

    const nextPointLog = [
      ...pointLog,
      {
        round: pointLog.length,
        starter,
        winCount,
        winner,
      },
    ]

    const nextScoreState: ScoreState = {
      ...scoreState,
      scoreLeft: side === 'left' ? scoreState.scoreLeft + 1 : scoreState.scoreLeft,
      scoreRight: side === 'right' ? scoreState.scoreRight + 1 : scoreState.scoreRight,
      serving: side,
      leftRightCourtPlayer:
        side === 'left' && side === scoreState.serving
          ? swapCourtPositions(scoreState.leftRightCourtPlayer)
          : scoreState.leftRightCourtPlayer,
      rightRightCourtPlayer:
        side === 'right' && side === scoreState.serving
          ? swapCourtPositions(scoreState.rightRightCourtPlayer)
          : scoreState.rightRightCourtPlayer,
    }

    setScoreHistory((current) => [...current, { pointLog, scoreState }])
    setPointLog(nextPointLog)
    setScoreState(nextScoreState)

    if (streakTitle) {
      setStreakAnnouncement({
        count: streakCount,
        key: Date.now(),
        teamName: side === 'left' ? getTeamDisplayName(leftTeam) : getTeamDisplayName(rightTeam),
        title: streakTitle,
      })
    }

    const reachedTarget =
      nextScoreState.scoreLeft >= nextScoreState.targetScore ||
      nextScoreState.scoreRight >= nextScoreState.targetScore

    if (reachedTarget) {
      setVictoryAnnouncement({
        key: Date.now() + 1,
        scoreLabel: `${nextScoreState.scoreLeft} : ${nextScoreState.scoreRight}`,
        teamName: side === 'left' ? getTeamDisplayName(leftTeam) : getTeamDisplayName(rightTeam),
        title: '拿下勝利',
      })
    }
  }

  const undoLastPoint = () => {
    const previous = scoreHistory.at(-1)

    if (!previous) {
      return
    }

    setScoreHistory((current) => current.slice(0, -1))
    setPointLog(previous.pointLog)
    setScoreState(previous.scoreState)
    setStreakAnnouncement(null)
    setVictoryAnnouncement(null)
  }

  const openSettlementDialog = () => {
    if (!leftTeam || !rightTeam || pointLog.length === 0) {
      return
    }

    setSettlement({
      error: '',
      open: true,
      uploading: false,
    })
  }

  const closeSettlementDialog = () => {
    if (settlement.uploading) {
      return
    }

    setSettlement((current) => ({
      ...current,
      error: '',
      open: false,
    }))
  }

  const skipUpload = () => {
    setSettlement({
      error: '',
      open: false,
      uploading: false,
    })
    resetScoring()
  }

  const uploadSettledMatch = async () => {
    if (!leftTeam || !rightTeam || !selectedGroup || pointLog.length === 0) {
      return
    }

    setSettlement((current) => ({
      ...current,
      error: '',
      uploading: true,
    }))

    try {
      const payload = buildHistoryPayload({
        leftTeam,
        pointLog,
        rightTeam,
        scoreState,
      })

      const result = await saveMatchHistory(payload)

      const historyItem: MatchHistoryItem = {
        id: String(result.id),
        playedAt: formatPlayedAt(payload.time),
        matchDate: targetDate,
        source: groupSource,
        groupId: selectedGroup.id,
        leftTeamName: getTeamDisplayName(leftTeam),
        rightTeamName: getTeamDisplayName(rightTeam),
        scoreLeft: scoreState.scoreLeft,
        scoreRight: scoreState.scoreRight,
        winner: getWinnerName(
          getTeamDisplayName(leftTeam),
          getTeamDisplayName(rightTeam),
          scoreState,
        ),
      }

      setHistory((current) => [historyItem, ...current])
      setSettlement({
        error: '',
        open: false,
        uploading: false,
      })
      resetScoring()
    } catch (error) {
      setSettlement({
        error: error instanceof Error ? error.message : '上傳戰績失敗。',
        open: true,
        uploading: false,
      })
    }
  }

  return (
    <div className={isScoreboardRoute ? 'app-shell app-shell-scoreboard' : 'app-shell'}>
      <header className={isScoreboardRoute ? 'topbar topbar-compact' : 'topbar'}>
        <div className="branding">
          <p className="eyebrow">Badminton Scoreboard</p>
          <h1>{isScoreboardRoute ? '羽毛球記分板' : '羽毛球分組與記分板'}</h1>
          {!isScoreboardRoute ? (
            <p className="intro-copy">
              先選日期或手動建立組別，再從該組挑選要對打的兩隊進入記分板。比賽結束後可直接上傳戰績到
              DB。
            </p>
          ) : null}
        </div>

        <nav className="topnav" aria-label="主要導覽">
          <NavLink className={({ isActive }) => (isActive ? 'nav-pill nav-pill-active' : 'nav-pill')} to="/teams">
            選隊伍
          </NavLink>
          <NavLink className={({ isActive }) => (isActive ? 'nav-pill nav-pill-active' : 'nav-pill')} to="/scoreboard">
            記分板
          </NavLink>
          <NavLink className={({ isActive }) => (isActive ? 'nav-pill nav-pill-active' : 'nav-pill')} to="/history">
            歷史戰績
          </NavLink>
        </nav>
      </header>

      <Routes>
        <Route
          path="/"
          element={
            <TeamSelectionPage
              areaAInput={areaAInput}
              areaBInput={areaBInput}
              groups={groups}
              groupSource={groupSource}
              loadMessage={loadMessage}
              loadStatus={loadStatus}
              targetDate={targetDate}
              onAreaAInputChange={setAreaAInput}
              onAreaBInputChange={setAreaBInput}
              onGenerateManualGroups={generateManualGroups}
              onLoadGroupsFromDb={() => void loadGroupsFromDb()}
              onTargetDateChange={setTargetDate}
              onUseGroup={selectGroup}
            />
          }
        />
        <Route
          path="/teams"
          element={
            <TeamSelectionPage
              areaAInput={areaAInput}
              areaBInput={areaBInput}
              groups={groups}
              groupSource={groupSource}
              loadMessage={loadMessage}
              loadStatus={loadStatus}
              targetDate={targetDate}
              onAreaAInputChange={setAreaAInput}
              onAreaBInputChange={setAreaBInput}
              onGenerateManualGroups={generateManualGroups}
              onLoadGroupsFromDb={() => void loadGroupsFromDb()}
              onTargetDateChange={setTargetDate}
              onUseGroup={selectGroup}
            />
          }
        />
        <Route
          path="/scoreboard"
          element={
            <ScoreboardPage
              currentSelectionOrder={getSelectionOrder(leftTeam, rightTeam)}
              finishDialogError={settlement.error}
              finishDialogOpen={settlement.open}
              finishDialogUploading={settlement.uploading}
              groupSource={groupSource}
              hasRecordedPoint={pointLog.length > 0}
              leftTeam={leftTeam}
              rightTeam={rightTeam}
              scoreState={scoreState}
              selectedGroup={selectedGroup}
              streakAnnouncement={streakAnnouncement}
              victoryAnnouncement={victoryAnnouncement}
              targetDate={targetDate}
              onApplyMatchup={applyMatchup}
              onCloseFinishDialog={closeSettlementDialog}
              onConfirmUpload={uploadSettledMatch}
              onOpenFinishDialog={openSettlementDialog}
              onRecordPoint={recordPoint}
              onSetServing={setServing}
              onSkipUpload={skipUpload}
              onSwapMatchup={swapMatchupSides}
              onSwapTeamPlayers={swapTeamPlayers}
              onUndoLastPoint={undoLastPoint}
            />
          }
        />
        <Route path="/history" element={<HistoryPage />} />
      </Routes>

      {pwaUpdateReady ? (
        <div className="pwa-update-toast" role="status" aria-live="polite">
          <div className="pwa-update-copy">
            <strong>有新版本可更新</strong>
            <span>點重新整理後套用最新版本。</span>
          </div>
          <button className="pwa-update-button" onClick={refreshForPwaUpdate} type="button">
            重新整理
          </button>
        </div>
      ) : null}
    </div>
  )
}

function buildHistoryPayload({
  leftTeam,
  pointLog,
  rightTeam,
  scoreState,
}: {
  leftTeam: GroupTeam
  pointLog: PointHistoryEntry[]
  rightTeam: GroupTeam
  scoreState: ScoreState
}): HistoryUploadPayload {
  const players = [
    leftTeam.playerA,
    leftTeam.playerB,
    rightTeam.playerB,
    rightTeam.playerA,
  ]

  return {
    dayOfWeek: new Date().getDay(),
    players,
    score: [scoreState.scoreLeft, scoreState.scoreRight],
    scoreList: pointLog.map((point) => [
      point.round,
      point.starter,
      point.winCount,
      point.winner,
    ]),
    team: [
      [leftTeam.playerA, leftTeam.playerB],
      [rightTeam.playerB, rightTeam.playerA],
    ],
    time: Math.floor(Date.now() / 1000),
    type: 0,
    winScore: scoreState.targetScore,
  }
}

function getServerHistoryIndex(
  state: ScoreState,
  leftTeam: GroupTeam,
  rightTeam: GroupTeam,
) {
  if (state.serving === 'left') {
    const server = getServingPlayer(leftTeam, state.leftRightCourtPlayer, state.scoreLeft)

    if (!server) {
      return null
    }

    return server.slot === 'playerA' ? 0 : 1
  }

  if (state.serving === 'right') {
    const server = getServingPlayer(rightTeam, state.rightRightCourtPlayer, state.scoreRight)

    if (!server) {
      return null
    }

    return server.slot === 'playerB' ? 2 : 3
  }

  return null
}

function formatPlayedAt(timestamp: number) {
  return new Date(timestamp * 1000).toLocaleString('zh-TW', { hour12: false })
}

function getSelectionOrder(leftTeam: GroupTeam | null, rightTeam: GroupTeam | null) {
  if (!leftTeam || !rightTeam) {
    return []
  }

  return [
    leftTeam.playerA,
    leftTeam.playerB,
    rightTeam.playerB,
    rightTeam.playerA,
  ].filter((name) => name.trim().length > 0)
}

function loadStoredText(storageKey: string, fallback: string) {
  const value = window.localStorage.getItem(storageKey)
  return value && value.trim() ? value : fallback
}

function loadStoredHistory(storageKey: string) {
  const value = window.localStorage.getItem(storageKey)

  if (!value) {
    return []
  }

  try {
    const parsed = JSON.parse(value) as MatchHistoryItem[]
    return Array.isArray(parsed) ? parsed : []
  } catch {
    return []
  }
}

export default App
