import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FolderInput, Play, RotateCcw, Upload } from 'lucide-react'
import { qk } from '@/shared/api/query-keys'
import type { MediaAssetDto, MediaAssetStatus } from '@/shared/api/types'
import { useApiError } from '@/shared/lib/use-api-error'
import { Badge, type BadgeProps } from '@/shared/ui/badge'
import { Button } from '@/shared/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/shared/ui/dialog'
import { HlsVideo } from '@/shared/ui/hls-video'
import { Pager } from '@/shared/ui/pager'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
import { toast } from '@/shared/ui/toast-store'
import { useTableSort } from '@/shared/lib/table-sort'
import { SortHeader } from '@/shared/ui/sortable'
import { deleteMedia, getMediaStats, listMedia, mediaPreviewUrl, retryMedia } from './api'
import { formatDuration, splitEta } from './format'
import { MediaImportDialog } from './MediaImportDialog'
import { useUploadStore } from './upload-store'

const PAGE_SIZE = 20

type MediaFilter = 'active' | 'Pending' | 'Processing' | 'all' | 'Ready' | 'Failed'

const filterStatuses: Record<MediaFilter, MediaAssetStatus[]> = {
  active: ['Pending', 'Processing'],
  Pending: ['Pending'],
  Processing: ['Processing'],
  all: [],
  Ready: ['Ready'],
  Failed: ['Failed'],
}

const statusVariant: Record<MediaAssetStatus, BadgeProps['variant']> = {
  Ready: 'default',
  Processing: 'muted',
  Pending: 'muted',
  Failed: 'destructive',
}

export function MediaPanel() {
  const { t } = useTranslation()
  const queryClient = useQueryClient()
  const fileInput = useRef<HTMLInputElement>(null)
  const [filter, setFilter] = useState<MediaFilter>('active')
  const [page, setPage] = useState(1)
  const { sort, toggle } = useTableSort('created', true)
  const [importOpen, setImportOpen] = useState(false)
  const [preview, setPreview] = useState<MediaAssetDto | null>(null)
  const enqueue = useUploadStore((s) => s.enqueue)

  const sortColumn = (key: string) => {
    setPage(1)
    toggle(key)
  }

  const { data, isLoading, refetch } = useQuery({
    queryKey: qk.media.list(filter, page, sort.key, sort.desc),
    queryFn: () =>
      listMedia({
        page,
        pageSize: PAGE_SIZE,
        statuses: filterStatuses[filter],
        sort: sort.key,
        desc: sort.desc,
      }),
    // Пока что-то обрабатывается — обновляем чаще, чтобы статус ехал в UI.
    refetchInterval: (query) =>
      query.state.data?.items.some((a) => a.status === 'Processing' || a.status === 'Pending')
        ? 4000
        : false,
  })

  const { data: stats } = useQuery({
    queryKey: qk.media.stats,
    queryFn: getMediaStats,
    // Пока есть незавершённая работа — освежаем чипы очереди/обработки.
    refetchInterval: (query) =>
      (query.state.data?.queued ?? 0) + (query.state.data?.processing ?? 0) > 0 ? 4000 : 15000,
  })

  /**
   * Сводка очереди опрашивается всегда, и она же служит датчиком: изменилась пара
   * «в очереди / в обработке» — значит какая-то запись сменила статус, и список пора перечитать.
   * Без этого список обновлялся бы, только пока активные записи видны на текущей странице: при
   * фильтре «Готовые» или пустом списке опрос выключался, и новый файл из inbox не появлялся.
   */
  const activity = `${stats?.queued ?? 0}:${stats?.processing ?? 0}`
  const lastActivity = useRef(activity)
  useEffect(() => {
    if (lastActivity.current === activity) return
    // Очередь опустела — говорим об этом вслух: обработка идёт минутами, и следить за чипами,
    // ожидая нуля, никто не должен. Сравниваем с прошлым состоянием, чтобы не поздравлять
    // с пустой очередью при каждом заходе на экран.
    const wasBusy = lastActivity.current !== '0:0'
    lastActivity.current = activity
    if (wasBusy && activity === '0:0') toast.success(t('admin.media.queueDrained'))
    void refetch()
  }, [activity, refetch, t])

  const invalidate = () => queryClient.invalidateQueries({ queryKey: qk.media.all })
  const onError = useApiError()

  const deleteMutation = useMutation({ mutationFn: deleteMedia, onSuccess: invalidate, onError })

  const retryMutation = useMutation({
    mutationFn: (id?: string) => retryMedia(id),
    onSuccess: (result) => {
      invalidate()
      toast.success(t('admin.media.retryQueued', { count: result.requeued }))
    },
    onError,
  })

  return (
    <div className="flex flex-col gap-4">
      <div className="flex flex-wrap items-center justify-between gap-2">
        <div className="flex items-center gap-3">
          <h2 className="crt-glow text-xl font-semibold">{t('admin.media.title')}</h2>
          <Select
            value={filter}
            onValueChange={(v) => {
              setPage(1)
              setFilter(v as MediaFilter)
            }}
          >
            <SelectTrigger className="w-40">
              <SelectValue />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="active">{t('admin.media.filterActive')}</SelectItem>
              <SelectItem value="Pending">{t('admin.media.statuses.Pending')}</SelectItem>
              <SelectItem value="Processing">{t('admin.media.statuses.Processing')}</SelectItem>
              <SelectItem value="all">{t('admin.media.filterAll')}</SelectItem>
              <SelectItem value="Ready">{t('admin.media.statuses.Ready')}</SelectItem>
              <SelectItem value="Failed">{t('admin.media.statuses.Failed')}</SelectItem>
            </SelectContent>
          </Select>

          {stats && (
            <div className="flex items-center gap-3 text-xs text-muted-foreground">
              <span title={t('admin.media.stats.queued')}>
                {t('admin.media.stats.queuedShort')}:{' '}
                <span className="text-foreground tabular-nums">{stats.queued}</span>
              </span>
              <span title={t('admin.media.stats.processing')}>
                {t('admin.media.stats.processingShort')}:{' '}
                <span className="text-foreground tabular-nums">{stats.processing}</span>
              </span>
              <span title={t('admin.media.stats.average')}>
                {t('admin.media.stats.averageShort')}:{' '}
                <span className="text-foreground tabular-nums">
                  {formatDuration(stats.averageProcessingSeconds)}
                </span>
              </span>
              {/* Оценка появляется только когда есть что ждать: «Осталось: —» бесполезно. */}
              {stats.estimatedRemainingSeconds !== null && (
                <span title={t('admin.media.stats.eta')}>
                  {t('admin.media.stats.etaShort')}:{' '}
                  <span className="text-foreground tabular-nums">
                    <EtaValue seconds={stats.estimatedRemainingSeconds} />
                  </span>
                </span>
              )}
            </div>
          )}
        </div>
        <div className="flex items-center gap-3">
          <input
            ref={fileInput}
            type="file"
            accept="video/*,.mkv,.avi,.ts"
            multiple
            className="hidden"
            onChange={(e) => {
              const files = e.target.files
              if (files && files.length > 0) void enqueue(Array.from(files))
              e.target.value = ''
            }}
          />
          {/* Массовый перезапуск виден только на фильтре ошибок: падают пачкой по одной причине,
              и чинить их поштучно — девять кликов вместо одного. */}
          {filter === 'Failed' && (data?.items.length ?? 0) > 0 && (
            <Button
              size="sm"
              variant="outline"
              disabled={retryMutation.isPending}
              onClick={() => retryMutation.mutate(undefined)}
            >
              <RotateCcw className="h-4 w-4" />
              {t('admin.media.retryAll')}
            </Button>
          )}
          <Button size="sm" variant="outline" onClick={() => setImportOpen(true)}>
            <FolderInput className="h-4 w-4" />
            {t('admin.media.importButton')}
          </Button>
          <Button size="sm" onClick={() => fileInput.current?.click()}>
            <Upload className="h-4 w-4" />
            {t('admin.media.upload')}
          </Button>
        </div>
      </div>

      {importOpen && <MediaImportDialog onClose={() => setImportOpen(false)} />}

      <div className="crt-panel overflow-x-auto rounded-md">
        <table className="w-full text-sm">
          <thead className="border-b border-border text-left text-muted-foreground">
            <tr>
              <SortHeader
                label={t('admin.media.name')}
                sortKey="name"
                sort={sort}
                onToggle={sortColumn}
              />
              <SortHeader
                label={t('admin.media.status')}
                sortKey="status"
                sort={sort}
                onToggle={sortColumn}
                className="whitespace-nowrap"
              />
              <SortHeader
                label={t('admin.media.duration')}
                sortKey="duration"
                sort={sort}
                onToggle={sortColumn}
              />
              <SortHeader
                label={t('admin.media.resolution')}
                sortKey="resolution"
                sort={sort}
                onToggle={sortColumn}
              />
              <SortHeader
                label={t('admin.media.processingTime')}
                sortKey="processing"
                sort={sort}
                onToggle={sortColumn}
              />
              <th className="px-4 py-2 font-medium">{t('common.actions')}</th>
            </tr>
          </thead>
          <tbody>
            {isLoading && (
              <tr>
                <td className="px-4 py-3 text-muted-foreground" colSpan={6}>
                  {t('common.loading')}
                </td>
              </tr>
            )}
            {data?.items.map((asset) => (
              <MediaRow
                key={asset.id}
                asset={asset}
                onPreview={() => setPreview(asset)}
                onRetry={() => retryMutation.mutate(asset.id)}
                onDelete={() => deleteMutation.mutate(asset.id)}
              />
            ))}
            {data?.items.length === 0 && !isLoading && (
              <tr>
                <td className="px-4 py-3 text-muted-foreground" colSpan={6}>
                  {t('admin.media.empty')}
                </td>
              </tr>
            )}
          </tbody>
        </table>
      </div>

      <Pager
        page={page}
        totalPages={data ? Math.max(1, Math.ceil(data.total / PAGE_SIZE)) : 1}
        onChange={setPage}
      />

      <Dialog open={preview !== null} onOpenChange={(open) => !open && setPreview(null)}>
        <DialogContent className="max-w-3xl">
          <DialogHeader>
            <DialogTitle className="truncate">{preview?.originalFileName}</DialogTitle>
          </DialogHeader>
          {/* Смотрят нарезку, а не исходник: в эфир идёт именно она, и битый звук или чёрный кадр
              видно только здесь. Кнопка в списке — это «плей», поэтому запускаем сразу. */}
          {preview && <HlsVideo src={mediaPreviewUrl(preview.id)} autoPlay />}
        </DialogContent>
      </Dialog>
    </div>
  )
}

/**
 * «Осталось: ~2 ч 40 мин». Тильда и округление до минут намеренные: это оценка по среднему времени,
 * и точная до секунды подпись читалась бы как обещание.
 */
function EtaValue({ seconds }: Readonly<{ seconds: number }>) {
  const { t } = useTranslation()
  const parts = splitEta(seconds)
  if (!parts) return <>—</>
  if (parts.hours === 0 && parts.minutes === 0) return <>{t('admin.media.stats.etaSoon')}</>

  const hours = parts.hours > 0 ? `${parts.hours} ${t('admin.media.stats.hoursShort')}` : ''
  const minutes = parts.minutes > 0 ? `${parts.minutes} ${t('admin.media.stats.minutesShort')}` : ''
  return <>~{[hours, minutes].filter(Boolean).join(' ')}</>
}

function MediaRow({
  asset,
  onPreview,
  onRetry,
  onDelete,
}: Readonly<{
  asset: MediaAssetDto
  onPreview: () => void
  onRetry: () => void
  onDelete: () => void
}>) {
  const { t } = useTranslation()
  return (
    <tr className="border-b border-border last:border-0">
      <td className="px-4 py-2">{asset.originalFileName}</td>
      <td className="whitespace-nowrap px-4 py-2">
        <Badge variant={statusVariant[asset.status]} title={asset.errorMessage ?? undefined}>
          {t(`admin.media.statuses.${asset.status}`)}
        </Badge>
      </td>
      <td className="px-4 py-2 text-muted-foreground">{formatDuration(asset.durationSeconds)}</td>
      <td className="px-4 py-2 text-muted-foreground">
        {asset.width && asset.height ? `${asset.width}×${asset.height}` : '—'}
      </td>
      <td className="px-4 py-2 text-muted-foreground tabular-nums">
        {asset.status === 'Ready' ? formatDuration(asset.processingSeconds) : '—'}
      </td>
      <td className="px-4 py-2">
        <div className="flex items-center gap-2">
          {/* Смотреть можно только нарезанное: у необработанного ассета плейлиста ещё нет. */}
          {asset.status === 'Ready' && (
            <Button
              size="sm"
              variant="outline"
              title={t('admin.media.preview')}
              onClick={onPreview}
            >
              <Play className="h-4 w-4" />
            </Button>
          )}
          {/* Файл на месте — упала только нарезка, и повторить её обычно и есть всё лечение. */}
          {asset.status === 'Failed' && (
            <Button size="sm" variant="outline" title={t('admin.media.retry')} onClick={onRetry}>
              <RotateCcw className="h-4 w-4" />
            </Button>
          )}
          <Button size="sm" variant="destructive" onClick={onDelete}>
            {t('common.delete')}
          </Button>
        </div>
      </td>
    </tr>
  )
}
