import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Link } from '@tanstack/react-router'
import { ChevronLeft, GripVertical, Search, Sparkles, Trash2, Undo2 } from 'lucide-react'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { qk } from '@/shared/api/query-keys'
import type {
  GroupCandidateDto,
  GroupElementKind,
  GroupFilter,
  GroupMode,
  ShowKind,
} from '@/shared/api/types'
import { useApiError } from '@/shared/lib/use-api-error'
import { Badge } from '@/shared/ui/badge'
import { Button } from '@/shared/ui/button'
import { Input } from '@/shared/ui/input'
import { Label } from '@/shared/ui/label'
import { toast } from '@/shared/ui/toast-store'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
import {
  addGroupElements,
  excludeGroupElement,
  findGroupCandidates,
  getGroup,
  removeGroupItem,
  reorderGroup,
  setGroupItemWeight,
  updateGroup,
} from './api'
import { DurationLabel } from './DurationLabel'
import { GroupFilterPanel } from './GroupFilterPanel'

/**
 * Что писать на бейдже позиции. Вид элемента («шоу») сам по себе бесполезен: в группе рекламы
 * все строки — шоу, и подпись ничего не говорит. Показываем тип шоу, когда он известен: «Ролик»,
 * «Сериал», «Полнометражка», — и «Коллекция» для франшизы.
 */
function elementLabelKey(elementKind: GroupElementKind, showKind: ShowKind | null) {
  return elementKind === 'Collection' || showKind === null
    ? `admin.groups.elementKinds.${elementKind}`
    : `admin.shows.kinds.${showKind}`
}

const EMPTY_FILTER: GroupFilter = {}

export function GroupDetail({ groupId }: Readonly<{ groupId: string }>) {
  const { t } = useTranslation()
  const queryClient = useQueryClient()

  const [name, setName] = useState<string | null>(null)
  const [description, setDescription] = useState<string | null>(null)
  const [filter, setFilter] = useState<GroupFilter | null>(null)
  const [candidates, setCandidates] = useState<GroupCandidateDto[] | null>(null)
  const [showWeights, setShowWeights] = useState(false)
  const [dragged, setDragged] = useState<string | null>(null)

  const { data: group, isLoading } = useQuery({
    queryKey: qk.groups.detail(groupId),
    queryFn: () => getGroup(groupId),
  })

  // Черновик правила поднимаем из сохранённого один раз — дальше им владеет форма.
  useEffect(() => {
    if (group && filter === null) setFilter(group.filter ?? EMPTY_FILTER)
  }, [group, filter])

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

  /**
   * Что подходит под сохранённое правило, но в группу ещё не попало: библиотека пополняется после
   * того, как группа собрана, и без этой проверки новое шоу лежало бы мимо эфира, пока кто-нибудь
   * не вспомнит нажать «Подобрать». Спрашиваем именно сохранённое правило (фильтр не передаём —
   * сервер берёт его сам), а не черновик формы: подсказка не должна прыгать, пока крутят поля.
   */
  const { data: pending } = useQuery({
    queryKey: qk.groups.pending(groupId),
    queryFn: () => findGroupCandidates(groupId, null),
    // Динамической группе подсказка не нужна: правило применяется само при каждом обращении.
    enabled: !!group?.filter && group?.mode === 'Static',
  })
  const pendingFresh = (pending ?? []).filter((c) => !c.alreadyInGroup)

  const saveMutation = useMutation({
    mutationFn: (mode?: GroupMode) =>
      updateGroup(groupId, {
        name: (name ?? group?.name ?? '').trim(),
        description: description ?? group?.description ?? null,
        filter: filter ?? null,
        mode,
      }),
    onSuccess: invalidate,
    onError,
  })

  const excludeMutation = useMutation({
    mutationFn: ({
      element,
      excluded,
    }: Readonly<{
      element: { elementKind: GroupElementKind; elementId: string }
      excluded: boolean
    }>) => excludeGroupElement(groupId, element, excluded),
    onSuccess: invalidate,
    onError,
  })

  const findMutation = useMutation({
    mutationFn: () => findGroupCandidates(groupId, filter ?? EMPTY_FILTER),
    onSuccess: setCandidates,
    onError,
  })

  const addMutation = useMutation({
    mutationFn: (elements: GroupCandidateDto[]) =>
      addGroupElements(
        groupId,
        elements.map((c) => ({ elementKind: c.elementKind, elementId: c.elementId })),
      ),
    onSuccess: (result) => {
      toast.success(t('admin.groups.added', { count: result.added }))
      setCandidates(null)
      invalidate()
    },
    onError,
  })

  const removeMutation = useMutation({
    mutationFn: (itemId: string) => removeGroupItem(groupId, itemId),
    onSuccess: invalidate,
    onError,
  })
  const weightMutation = useMutation({
    mutationFn: ({ itemId, weight }: Readonly<{ itemId: string; weight: number }>) =>
      setGroupItemWeight(groupId, itemId, weight),
    onSuccess: invalidate,
    onError,
  })
  const reorderMutation = useMutation({
    mutationFn: (order: string[]) => reorderGroup(groupId, order),
    onSuccess: invalidate,
    onError,
  })

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

  const dropOn = (targetItemId: string) => {
    if (!dragged || dragged === targetItemId) return
    // Порядок задаётся идентификаторами позиций, поэтому найденное правилом (без своей строки)
    // в перестановку не входит — его место определяет алфавит.
    const order = group.items
      .map((i) => i.id)
      .filter((id): id is string => id !== null && id !== dragged)
    order.splice(order.indexOf(targetItemId), 0, dragged)
    setDragged(null)
    reorderMutation.mutate(order)
  }

  const fresh = (candidates ?? []).filter((c) => !c.alreadyInGroup)
  const isDynamic = group.mode === 'Dynamic'
  // Закреплённое, которого черновик правила не находит, в составе всё равно останется.
  const pinnedNotMatched = group.items.filter(
    (i) => i.pinned && !(candidates ?? []).some((c) => c.elementId === i.elementId),
  ).length

  return (
    <div className="flex flex-col gap-6">
      <div>
        <Button asChild size="sm" variant="ghost">
          <Link to="/admin/groups">
            <ChevronLeft className="h-4 w-4" />
            {t('admin.groups.title')}
          </Link>
        </Button>
      </div>

      <div className="flex flex-wrap items-end gap-3">
        <div className="flex flex-col gap-1.5">
          <Label>{t('admin.groups.name')}</Label>
          <Input
            className="w-64"
            value={name ?? group.name}
            maxLength={256}
            onChange={(e) => setName(e.target.value)}
          />
        </div>
        <div className="flex min-w-0 flex-1 flex-col gap-1.5">
          <Label>{t('admin.groups.description')}</Label>
          <Input
            value={description ?? group.description ?? ''}
            maxLength={2048}
            onChange={(e) => setDescription(e.target.value)}
          />
        </div>
        <Button
          size="sm"
          disabled={saveMutation.isPending}
          onClick={() => saveMutation.mutate(undefined)}
        >
          {t('common.save')}
        </Button>
      </div>

      {/* Режим — первое, что нужно понять про группу: от него зависит, чем является список ниже. */}
      <div className="flex flex-wrap items-center gap-3 text-sm">
        <span className="font-medium">{t('admin.groups.mode.label')}:</span>
        <Select
          value={group.mode}
          onValueChange={(value) => saveMutation.mutate(value as GroupMode)}
        >
          <SelectTrigger className="h-8 w-56">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="Static">{t('admin.groups.mode.Static')}</SelectItem>
            <SelectItem value="Dynamic">{t('admin.groups.mode.Dynamic')}</SelectItem>
          </SelectContent>
        </Select>
        <span className="text-xs text-muted-foreground">
          {t(`admin.groups.mode.${group.mode}Hint`)}
        </span>
      </div>

      <div className="flex flex-wrap items-center gap-3 text-sm text-muted-foreground">
        <Badge variant="muted">
          {t('admin.groups.items')}: {group.itemCount}
        </Badge>
        <Badge variant="muted">
          {t('admin.groups.units')}: {group.unitCount}
        </Badge>
        <Badge variant="muted">
          <DurationLabel seconds={group.totalDurationSeconds} />
        </Badge>
      </div>

      {pendingFresh.length > 0 && (
        <div className="crt-panel flex flex-wrap items-center gap-3 rounded-md border border-primary/40 px-4 py-3 text-sm">
          <Sparkles className="h-4 w-4 shrink-0 text-primary" />
          <span className="min-w-0 flex-1">
            {t('admin.groups.pendingFound', { count: pendingFresh.length })}{' '}
            <span className="text-muted-foreground">
              {pendingFresh
                .slice(0, 3)
                .map((c) => c.elementName)
                .join(', ')}
              {pendingFresh.length > 3 && '…'}
            </span>
          </span>
          <Button
            size="sm"
            disabled={addMutation.isPending}
            onClick={() => addMutation.mutate(pendingFresh)}
          >
            {t('admin.groups.pendingAdd', { count: pendingFresh.length })}
          </Button>
        </div>
      )}

      <div className="grid gap-6 lg:grid-cols-2">
        {/* Левая панель — конструктор правила набора */}
        <div className="crt-panel flex flex-col gap-4 rounded-md p-4">
          <div className="flex flex-col gap-1">
            <h3 className="text-sm font-semibold uppercase tracking-wide">
              {t('admin.groups.filter.title')}
            </h3>
            <p className="text-xs text-muted-foreground">{t('admin.groups.filter.hint')}</p>
          </div>

          <GroupFilterPanel filter={filter ?? EMPTY_FILTER} onChange={setFilter} />

          <div className="flex flex-wrap items-center gap-2">
            <Button
              size="sm"
              variant="outline"
              disabled={findMutation.isPending}
              onClick={() => findMutation.mutate()}
            >
              <Search className="h-4 w-4" /> {t('admin.groups.find')}
            </Button>
            {candidates !== null && isDynamic && (
              // У группы по правилу «добавить найденное» не имеет смысла — правило и есть состав.
              // Показываем, каким станет состав, если сохранить черновик правила.
              <span className="text-sm text-muted-foreground">
                {t('admin.groups.previewComposition', {
                  count: candidates.length + pinnedNotMatched,
                })}
              </span>
            )}
            {candidates !== null && !isDynamic && (
              <>
                <span className="text-sm text-muted-foreground">
                  {t('admin.groups.found', { total: candidates.length, fresh: fresh.length })}
                </span>
                <Button
                  size="sm"
                  disabled={fresh.length === 0 || addMutation.isPending}
                  onClick={() => addMutation.mutate(fresh)}
                >
                  {t('admin.groups.addFound')}
                </Button>
              </>
            )}
          </div>

          {candidates !== null && candidates.length > 0 && (
            <ul className="max-h-72 divide-y divide-border overflow-y-auto rounded border border-border text-sm">
              {candidates.map((candidate) => (
                <li
                  key={`${candidate.elementKind}:${candidate.elementId}`}
                  className="flex items-center gap-2 px-3 py-1.5"
                >
                  <span className="min-w-0 flex-1 truncate">{candidate.elementName}</span>
                  {candidate.year && (
                    <span className="text-muted-foreground">{candidate.year}</span>
                  )}
                  <Badge variant="muted">
                    {t(elementLabelKey(candidate.elementKind, candidate.showKind))}
                  </Badge>
                  {candidate.alreadyInGroup && (
                    <Badge variant="muted">{t('admin.groups.alreadyIn')}</Badge>
                  )}
                </li>
              ))}
            </ul>
          )}
        </div>

        {/* Правая панель — состав */}
        <div className="crt-panel flex flex-col gap-3 rounded-md p-4">
          <div className="flex flex-wrap items-center justify-between gap-2">
            <h3 className="text-sm font-semibold uppercase tracking-wide">
              {t('admin.groups.composition')}
            </h3>
            <Button size="sm" variant="ghost" onClick={() => setShowWeights((v) => !v)}>
              {showWeights ? t('admin.groups.hideAdvanced') : t('admin.groups.showAdvanced')}
            </Button>
          </div>
          <p className="text-xs text-muted-foreground">
            {isDynamic ? t('admin.groups.dynamicOrderHint') : t('admin.groups.orderHint')}
          </p>

          {group.items.length === 0 ? (
            <p className="text-sm text-muted-foreground">{t('admin.groups.empty')}</p>
          ) : (
            <ul className="divide-y divide-border text-sm">
              {group.items.map((item, index) => {
                // Перетаскивать и взвешивать можно только то, у чего есть своя строка: найденному
                // правилом порядок задаёт алфавит, а вес всегда единица.
                const editable = item.id !== null
                return (
                  <li
                    key={`${item.elementKind}:${item.elementId}`}
                    draggable={editable && !isDynamic}
                    onDragStart={() => item.id && setDragged(item.id)}
                    onDragOver={(e) => e.preventDefault()}
                    onDrop={() => item.id && dropOn(item.id)}
                    className="flex items-center gap-2 py-2"
                  >
                    <GripVertical
                      className={`h-4 w-4 shrink-0 text-muted-foreground ${
                        editable && !isDynamic ? 'cursor-grab' : 'opacity-30'
                      }`}
                    />
                    <span className="w-6 shrink-0 text-muted-foreground">{index + 1}</span>
                    <span className="min-w-0 flex-1 truncate">{item.elementName}</span>
                    {isDynamic && (
                      <Badge variant={item.pinned ? 'default' : 'muted'}>
                        {item.pinned ? t('admin.groups.pinned') : t('admin.groups.byRule')}
                      </Badge>
                    )}
                    <Badge variant="muted">
                      {t(elementLabelKey(item.elementKind, item.showKind))}
                    </Badge>
                    <span className="text-muted-foreground">
                      {t('admin.groups.units')}: {item.unitCount}
                    </span>
                    {showWeights && editable && (
                      <Input
                        type="number"
                        min={0}
                        className="h-8 w-20"
                        defaultValue={item.weight}
                        onBlur={(e) => {
                          const weight = Number(e.target.value)
                          if (item.id && Number.isFinite(weight) && weight !== item.weight)
                            weightMutation.mutate({ itemId: item.id, weight })
                        }}
                      />
                    )}
                    <Button
                      size="sm"
                      variant="ghost"
                      title={isDynamic ? t('admin.groups.excludeHint') : undefined}
                      onClick={() =>
                        isDynamic
                          ? excludeMutation.mutate({
                              element: {
                                elementKind: item.elementKind,
                                elementId: item.elementId,
                              },
                              excluded: true,
                            })
                          : item.id && removeMutation.mutate(item.id)
                      }
                    >
                      <Trash2 className="h-4 w-4" />
                    </Button>
                  </li>
                )
              })}
            </ul>
          )}

          {/* Исключённое видно списком: иначе непонятно, почему шоу нет в составе. */}
          {isDynamic && group.excluded.length > 0 && (
            <div className="flex flex-col gap-1 border-t border-border pt-2">
              <span className="text-xs uppercase tracking-wide text-muted-foreground">
                {t('admin.groups.excluded')}
              </span>
              <ul className="flex flex-col gap-1 text-sm">
                {group.excluded.map((element) => (
                  <li
                    key={`${element.elementKind}:${element.elementId}`}
                    className="flex items-center gap-2"
                  >
                    <span className="min-w-0 flex-1 truncate text-muted-foreground">
                      {element.elementName}
                    </span>
                    <Button
                      size="sm"
                      variant="ghost"
                      onClick={() =>
                        excludeMutation.mutate({
                          element: {
                            elementKind: element.elementKind,
                            elementId: element.elementId,
                          },
                          excluded: false,
                        })
                      }
                    >
                      <Undo2 className="h-4 w-4" />
                      {t('admin.groups.restore')}
                    </Button>
                  </li>
                ))}
              </ul>
            </div>
          )}
        </div>
      </div>
    </div>
  )
}
