import { useCallback, useEffect, useRef, useState } from 'react'
import {
  publishPvpMatchState,
  requestPvpRematch,
  type PvpMatchSnapshot,
  type PvpMatchSide,
  type PvpMatchStatus,
  type PvpRematchResponse,
} from './pvpRoguelike'

export type LiveMatchProgressPayload<TSideState> = {
  state: TSideState
  status: PvpMatchStatus
  stage: number
  encounterIndex: number
  encountersCleared: number
  enemyHealth: number
  alive: boolean
  elapsedTicks: number
}

type StartLiveMatchSyncOptions<TSideState> = {
  matchId: string
  intervalMs?: number
  getPayload: () => LiveMatchProgressPayload<TSideState>
  onSnapshot: (snapshot: PvpMatchSnapshot<TSideState>) => void
  onError?: (reason: unknown) => void
}

type RequestLiveRematchOptions<TSideState> = {
  matchId: string
  maxPendingAttempts?: number
  maxFailureAttempts?: number
  pendingDelayMs?: number
  failureDelayMs?: number
  onMatched: (result: Required<Pick<PvpRematchResponse<TSideState>, 'match' | 'side'>>) => void
  onExpired: () => void
  onFailure: (reason: unknown) => void
}

type LivePvpMatchBase = {
  id: string
  opponentName: string
}

type UsePvpLiveMatchSyncOptions<TSideState, TLiveMatch extends LivePvpMatchBase> = {
  liveMatch: TLiveMatch | null
  syncEnabled: boolean
  getPayload: (liveMatch: TLiveMatch) => LiveMatchProgressPayload<TSideState>
  onSnapshot: (snapshot: PvpMatchSnapshot<TSideState>, liveMatch: TLiveMatch) => void
  onError?: (reason: unknown) => void
  startLiveMatch: (match: PvpMatchSnapshot<TSideState>, side: PvpMatchSide, message?: string) => void
}

type RematchState = {
  matchId: string | null
  requested: boolean
  message: string
}

/**
 * React wrapper for live PvP state sync and rematch UX. Mode screens provide
 * payload and snapshot rules; shared polling and rematch state live here.
 */
export function usePvpLiveMatchSync<TSideState, TLiveMatch extends LivePvpMatchBase>({
  liveMatch,
  syncEnabled,
  getPayload,
  onSnapshot,
  onError,
  startLiveMatch,
}: UsePvpLiveMatchSyncOptions<TSideState, TLiveMatch>) {
  const [rematchState, setRematchState] = useState<RematchState>({
    matchId: null,
    requested: false,
    message: '',
  })
  const getPayloadRef = useRef(getPayload)
  const onSnapshotRef = useRef(onSnapshot)
  const onErrorRef = useRef(onError)
  const startLiveMatchRef = useRef(startLiveMatch)
  const liveMatchId = liveMatch?.id

  useEffect(() => {
    getPayloadRef.current = getPayload
    onSnapshotRef.current = onSnapshot
    onErrorRef.current = onError
    startLiveMatchRef.current = startLiveMatch
  }, [getPayload, onError, onSnapshot, startLiveMatch])

  useEffect(() => {
    if (!liveMatch || !syncEnabled) return undefined
    return startLiveMatchSync<TSideState>({
      matchId: liveMatch.id,
      getPayload: () => getPayloadRef.current(liveMatch),
      onSnapshot: (snapshot) => onSnapshotRef.current(snapshot, liveMatch),
      onError: (reason) => onErrorRef.current?.(reason),
    })
  }, [liveMatch, syncEnabled])

  const handleRematch = useCallback(() => {
    if (!liveMatch || (rematchState.matchId === liveMatch.id && rematchState.requested)) return undefined
    const { id, opponentName } = liveMatch
    setRematchState({
      matchId: id,
      requested: true,
      message: `Waiting for ${opponentName} to rematch...`,
    })
    return requestLiveRematch<TSideState>({
      matchId: id,
      onMatched: (result) => {
        startLiveMatchRef.current(result.match, result.side, `Rematch against ${opponentName} begins.`)
      },
      onExpired: () => {
        setRematchState({ matchId: id, requested: false, message: 'Rematch expired.' })
      },
      onFailure: (reason) => {
        setRematchState({
          matchId: id,
          requested: false,
          message: reason instanceof Error ? reason.message : 'Unable to request rematch.',
        })
      },
    })
  }, [liveMatch, rematchState.matchId, rematchState.requested])

  const currentRematchState = rematchState.matchId === liveMatchId
    ? rematchState
    : { matchId: liveMatchId ?? null, requested: false, message: '' }

  return {
    rematchRequested: currentRematchState.requested,
    rematchMessage: currentRematchState.message,
    handleRematch,
  }
}

/**
 * Polls the backend rematch endpoint until both players accept, the request
 * expires, or repeated network failures make the rematch unavailable.
 */
export function requestLiveRematch<TSideState>({
  matchId,
  maxPendingAttempts = 180,
  maxFailureAttempts = 10,
  pendingDelayMs = 700,
  failureDelayMs = 900,
  onMatched,
  onExpired,
  onFailure,
}: RequestLiveRematchOptions<TSideState>) {
  let cancelled = false
  let attempts = 0

  const schedule = (callback: () => void, delay: number) => {
    window.setTimeout(callback, delay)
  }

  const handleResponse = (result: PvpRematchResponse<TSideState>) => {
    if (cancelled) return
    if (result.status === 'matched' && result.match && result.side) {
      onMatched({ match: result.match, side: result.side })
      return
    }
    attempts += 1
    if (attempts >= maxPendingAttempts) {
      onExpired()
      return
    }
    schedule(pollRematch, pendingDelayMs)
  }

  const pollRematch = () => {
    requestPvpRematch<TSideState>(matchId)
      .then(handleResponse)
      .catch((reason: unknown) => {
        if (cancelled) return
        attempts += 1
        if (attempts >= maxFailureAttempts) {
          onFailure(reason)
          return
        }
        schedule(pollRematch, failureDelayMs)
      })
  }

  pollRematch()
  return () => {
    cancelled = true
  }
}

/**
 * Publishes local live-match progress and polls opponent state on a fixed
 * interval. Mode-specific snapshot handling stays in the caller.
 */
export function startLiveMatchSync<TSideState>({
  matchId,
  intervalMs = 700,
  getPayload,
  onSnapshot,
  onError = () => undefined,
}: StartLiveMatchSyncOptions<TSideState>) {
  let stopped = false
  const syncMatch = () => {
    publishPvpMatchState<TSideState>(matchId, getPayload())
      .then((snapshot) => {
        if (!stopped) onSnapshot(snapshot)
      })
      .catch((reason: unknown) => {
        if (!stopped) onError(reason)
      })
  }

  syncMatch()
  const timer = window.setInterval(syncMatch, intervalMs)
  return () => {
    stopped = true
    window.clearInterval(timer)
  }
}
