import { useQuery } from '@tanstack/react-query'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Download, Radio, RotateCw } from 'lucide-react'
import { qk } from '@/shared/api/query-keys'
import type { PublicEpgEntryDto } from '@/shared/api/types'
import { cn } from '@/shared/lib/cn'
import { Badge } from '@/shared/ui/badge'
import { Button } from '@/shared/ui/button'
import { toast } from '@/shared/ui/toast-store'
import { ChannelPlayer } from './ChannelPlayer'
import {
  downloadIptvPlaylist,
  getEpg,
  getViewerFeatures,
  imageUrl,
  listChannels,
  watchChannel,
} from './api'

function formatTime(iso: string) {
  return new Date(iso).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
}

/** Кадр серии, если он есть; иначе постер шоу (он вертикальный, отсюда другая ширина). */
function EntryThumb({ entry }: Readonly<{ entry: PublicEpgEntryDto | undefined }>) {
  if (entry?.episodeStillImageId)
    return (
      <img
        src={imageUrl(entry.episodeStillImageId)}
        alt=""
        className="hidden h-24 w-40 shrink-0 rounded object-cover sm:block"
      />
    )

  if (entry?.showPosterImageId)
    return (
      <img
        src={imageUrl(entry.showPosterImageId)}
        alt=""
        className="hidden h-24 w-16 shrink-0 rounded object-cover sm:block"
      />
    )

  return null
}

export function AirPage() {
  const { t } = useTranslation()
  const [selected, setSelected] = useState<string | null>(null)
  const [watchReady, setWatchReady] = useState(false)
  const [playerError, setPlayerError] = useState(false)
  const [attempt, setAttempt] = useState(0)
  const [flash, setFlash] = useState(false)

  const handleUnavailable = useCallback(() => setPlayerError(true), [])
  const retry = () => {
    setPlayerError(false)
    setAttempt((a) => a + 1)
  }

  const [downloading, setDownloading] = useState(false)
  const downloadPlaylist = async () => {
    setDownloading(true)
    try {
      await downloadIptvPlaylist()
      // Предупреждение показываем в момент скачивания, а не подписью под кнопкой: висеть на экране
      // ему незачем, а прочитать его надо ровно тогда, когда файл уже на руках.
      toast.success(t('air.iptvDone'))
    } catch {
      toast.error(t('air.iptvFailed'))
    } finally {
      setDownloading(false)
    }
  }

  const { data: channels, isLoading } = useQuery({
    queryKey: qk.air.channels,
    queryFn: listChannels,
  })
  const { data: features } = useQuery({ queryKey: qk.air.features, queryFn: getViewerFeatures })

  const numbersEnabled = features?.channelNumbersEnabled ?? false
  const currentChannel = channels?.find((c) => c.slug === selected)

  /**
   * Переключение по номерам: список уже отсортирован сервером, поэтому «вверх-вниз» — это шаг
   * по нему. Короткий чёрный кадр с номером ставится сразу, до готовности потока.
   */
  const step = useCallback(
    (delta: number) => {
      if (!channels || channels.length === 0) return
      const index = channels.findIndex((c) => c.slug === selected)
      const next = channels[(index + delta + channels.length) % channels.length]
      if (!next || next.slug === selected) return
      setFlash(true)
      setSelected(next.slug)
    },
    [channels, selected],
  )

  useEffect(() => {
    if (!flash) return
    const id = window.setTimeout(() => setFlash(false), 900)
    return () => window.clearTimeout(id)
  }, [flash, selected])

  useEffect(() => {
    if (!numbersEnabled) return
    const onKey = (event: KeyboardEvent) => {
      // Не перехватываем стрелки, пока фокус в поле ввода: там они двигают каретку.
      const target = event.target as HTMLElement | null
      if (target && ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)) return
      if (event.key === 'ArrowUp' || event.key === 'PageUp') {
        event.preventDefault()
        step(-1)
      } else if (event.key === 'ArrowDown' || event.key === 'PageDown') {
        event.preventDefault()
        step(1)
      }
    }
    window.addEventListener('keydown', onKey)
    return () => window.removeEventListener('keydown', onKey)
  }, [numbersEnabled, step])

  useEffect(() => {
    if (!selected && channels && channels.length > 0) setSelected(channels[0].slug)
  }, [channels, selected])

  useEffect(() => {
    if (!selected) return
    setWatchReady(false)
    setPlayerError(false)
    let cancelled = false
    void watchChannel(selected)
      .then(() => {
        if (!cancelled) setWatchReady(true)
      })
      .catch(() => {
        // Выдача stream-cookie не удалась (403/500/сеть) — показываем offline-панель с кнопкой ретрая,
        // а не бесконечный скелетон. Ретрай (attempt) заново дёрнет watchChannel.
        if (!cancelled) {
          setWatchReady(true)
          setPlayerError(true)
        }
      })
    return () => {
      cancelled = true
    }
  }, [selected, attempt])

  // Stream-cookie короткоживущий (TTL на сервере ~30 мин) — периодически перевыпускаем, пока смотрим,
  // иначе плейлист/сегменты начнут отдавать 401 посреди эфира. Тихо: ошибку словит перезагрузка плейлиста.
  useEffect(() => {
    if (!selected || playerError) return
    const id = window.setInterval(() => {
      void watchChannel(selected).catch(() => undefined)
    }, 20 * 60_000)
    return () => window.clearInterval(id)
  }, [selected, playerError])

  const { data: epg } = useQuery({
    queryKey: qk.air.epg(selected),
    queryFn: () =>
      getEpg(selected!, new Date(Date.now() - 30 * 60_000), new Date(Date.now() + 3 * 60 * 60_000)),
    enabled: !!selected,
    refetchInterval: 60_000,
  })

  const { current, upcoming, currentEntry } = useMemo(() => buildGuide(epg ?? []), [epg])

  // Плашка «Далее» — только на исходе программы: висеть весь эфир ей незачем.
  const nextUp =
    current && upcoming.length > 0 && new Date(current.endsAtUtc).getTime() - Date.now() < 60_000
      ? upcoming[0].showName
      : null

  if (isLoading) return <p className="text-muted-foreground">{t('common.loading')}</p>

  if (!channels || channels.length === 0)
    return (
      <div className="flex flex-col gap-2">
        <h1 className="crt-glow text-2xl font-bold">{t('nav.dashboard')}</h1>
        <p className="text-muted-foreground">{t('air.noChannels')}</p>
      </div>
    )

  return (
    <div className="flex flex-col gap-4">
      <div className="flex flex-wrap items-center justify-between gap-2">
        <h1 className="crt-glow text-2xl font-bold">{t('nav.dashboard')}</h1>
        <div className="flex flex-wrap items-center gap-3">
          {numbersEnabled && (
            <span className="text-xs text-muted-foreground">{t('air.numbersHint')}</span>
          )}
          <Button
            size="sm"
            variant="outline"
            disabled={downloading}
            title={t('air.iptvHint')}
            onClick={downloadPlaylist}
          >
            <Download className="h-4 w-4" />
            {t('air.iptv')}
          </Button>
        </div>
      </div>

      <div className="grid gap-4 md:grid-cols-[220px_1fr]">
        <aside className="flex gap-2 overflow-x-auto md:flex-col md:overflow-visible">
          {channels.map((channel) => (
            <button
              key={channel.id}
              type="button"
              onClick={() => setSelected(channel.slug)}
              className={cn(
                'flex shrink-0 items-center gap-2 rounded-sm border border-border px-3 py-2 text-left text-sm hover:bg-muted md:shrink',
                selected === channel.slug && 'border-primary text-primary',
              )}
            >
              {numbersEnabled && channel.number !== null && (
                <span className="w-6 shrink-0 text-right text-xs tabular-nums text-muted-foreground">
                  {channel.number}
                </span>
              )}
              {channel.currentShowPosterImageId ? (
                <img
                  src={imageUrl(channel.currentShowPosterImageId)}
                  alt=""
                  className="h-10 w-7 shrink-0 rounded object-cover"
                />
              ) : (
                <Radio className="h-4 w-4 shrink-0" />
              )}
              <span className="flex min-w-0 flex-col">
                <span className="truncate">{channel.name}</span>
                {channel.currentShowName && (
                  <span className="truncate text-xs text-muted-foreground">
                    {channel.currentShowName}
                  </span>
                )}
              </span>
            </button>
          ))}
        </aside>

        <div className="flex flex-col gap-4">
          {/* До выдачи cookie tw_stream плеер грузить нечем — держим место заглушкой. */}
          {(!selected || !watchReady) && (
            <div className="aspect-video w-full animate-pulse rounded-md border border-border bg-black" />
          )}
          {selected && watchReady && playerError && (
            <div className="crt-panel flex aspect-video w-full flex-col items-center justify-center gap-3 rounded-md text-center">
              <Radio className="h-10 w-10 text-muted-foreground" strokeWidth={1} />
              <div className="flex flex-col gap-1">
                <p className="font-medium">{t('air.offline')}</p>
                <p className="text-sm text-muted-foreground">{t('air.offlineHint')}</p>
              </div>
              <Button size="sm" variant="outline" onClick={retry}>
                <RotateCw className="h-4 w-4" />
                {t('air.retry')}
              </Button>
            </div>
          )}
          {selected && watchReady && !playerError && (
            <ChannelPlayer
              key={`${selected}-${attempt}`}
              slug={selected}
              channel={currentChannel}
              nextUp={nextUp}
              flash={flash}
              onUnavailable={handleUnavailable}
            />
          )}

          <div className="flex flex-col gap-3">
            {current && (
              <div className="crt-panel flex gap-3 rounded-md p-3">
                <EntryThumb entry={currentEntry} />
                <div className="flex min-w-0 flex-col gap-1">
                  <div className="flex items-center gap-2">
                    <Badge>{t('air.now')}</Badge>
                    <span className="font-medium">{current.showName}</span>
                  </div>
                  {currentEntry?.episodeTitle && (
                    <span className="text-sm">{currentEntry.episodeTitle}</span>
                  )}
                  <span className="text-xs text-muted-foreground">
                    {formatTime(current.startsAtUtc)} – {formatTime(current.endsAtUtc)}
                  </span>
                  {currentEntry?.episodeOverview && (
                    <p className="line-clamp-3 text-xs text-muted-foreground">
                      {currentEntry.episodeOverview}
                    </p>
                  )}
                </div>
              </div>
            )}

            {upcoming.length > 0 && (
              <div className="crt-panel rounded-md">
                <div className="border-b border-border px-4 py-2 text-xs uppercase tracking-wide text-muted-foreground">
                  {t('air.next')}
                </div>
                <ul className="divide-y divide-border">
                  {upcoming.slice(0, 6).map((block) => (
                    <li key={block.key} className="flex items-center gap-3 px-4 py-2 text-sm">
                      <span className="shrink-0 whitespace-nowrap tabular-nums text-muted-foreground">
                        {formatTime(block.startsAtUtc)} – {formatTime(block.endsAtUtc)}
                      </span>
                      <span>{block.showName}</span>
                    </li>
                  ))}
                </ul>
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  )
}

type GuideBlock = {
  key: string
  showId: string | null
  showName: string
  startsAtUtc: string
  endsAtUtc: string
}

/**
 * Строит телегид: рекламу/заставки не показываем, а подряд идущие серии одного шоу склеиваем в один
 * блок с диапазоном «с – по». Отдельно возвращаем текущую серию (для метаданных карточки «сейчас»).
 */
function buildGuide(entries: PublicEpgEntryDto[]): {
  current?: GuideBlock
  upcoming: GuideBlock[]
  currentEntry?: PublicEpgEntryDto
} {
  const blocks: GuideBlock[] = []
  for (const entry of entries) {
    if (entry.kind !== 'Program') continue
    const last = blocks.at(-1)
    if (last?.showId === entry.showId) {
      last.endsAtUtc = entry.endsAtUtc
    } else {
      blocks.push({
        key: entry.startsAtUtc,
        showId: entry.showId,
        showName: entry.showName ?? '—',
        startsAtUtc: entry.startsAtUtc,
        endsAtUtc: entry.endsAtUtc,
      })
    }
  }

  const now = Date.now()
  const active = (start: string, end: string) =>
    new Date(start).getTime() <= now && new Date(end).getTime() > now
  const current = blocks.find((b) => active(b.startsAtUtc, b.endsAtUtc))
  const upcoming = blocks.filter((b) => new Date(b.startsAtUtc).getTime() > now)
  const currentEntry = entries.find(
    (e) => e.kind === 'Program' && active(e.startsAtUtc, e.endsAtUtc),
  )
  return { current, upcoming, currentEntry }
}
