import {
  CheckSquare,
  Copy,
  Image as ImageIcon,
  RefreshCcw,
  Replace,
  Save,
  Square,
  Trash2,
  Upload,
} from 'lucide-react'
import { startTransition, useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'

import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Select } from '@/components/ui/select'
import { Skeleton } from '@/components/ui/skeleton'
import { adminApi, ApiError } from '@/lib/api'
import {
  formatCompressionPreview,
  maybeCompressImageWithPrompt,
  normalizeCoverImageWithPrompt,
} from '@/lib/image-compress'
import type { AdminMediaObjectResponse } from '@/lib/types'
import { FormField } from '@/components/form-field'
import { Textarea } from '@/components/ui/textarea'

function formatBytes(value: number) {
  if (!Number.isFinite(value) || value <= 0) {
    return '0 B'
  }
  const units = ['B', 'KB', 'MB', 'GB']
  let size = value
  let unitIndex = 0
  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024
    unitIndex += 1
  }
  return `${size >= 10 || unitIndex === 0 ? size.toFixed(0) : size.toFixed(1)} ${units[unitIndex]}`
}

type MediaMetadataFormState = {
  title: string
  altText: string
  caption: string
  tags: string
  notes: string
}

const defaultMetadataForm: MediaMetadataFormState = {
  title: '',
  altText: '',
  caption: '',
  tags: '',
  notes: '',
}

function normalizeMediaTags(value: unknown): string[] {
  if (!Array.isArray(value)) {
    return []
  }

  return value
    .filter((item): item is string => typeof item === 'string')
    .map((item) => item.trim())
    .filter(Boolean)
}

function normalizeMediaItem(item: AdminMediaObjectResponse): AdminMediaObjectResponse {
  return {
    ...item,
    tags: normalizeMediaTags(item.tags),
  }
}

function toMetadataForm(item: AdminMediaObjectResponse | null): MediaMetadataFormState {
  if (!item) {
    return defaultMetadataForm
  }

  return {
    title: item.title ?? '',
    altText: item.alt_text ?? '',
    caption: item.caption ?? '',
    tags: normalizeMediaTags(item.tags).join(', '),
    notes: item.notes ?? '',
  }
}

function parseTagList(value: string) {
  return Array.from(
    new Set(
      value
        .split(',')
        .map((item) => item.trim())
        .filter(Boolean),
    ),
  )
}

export function MediaPage() {
  const [items, setItems] = useState<AdminMediaObjectResponse[]>([])
  const [loading, setLoading] = useState(true)
  const [refreshing, setRefreshing] = useState(false)
  const [deletingKey, setDeletingKey] = useState<string | null>(null)
  const [replacingKey, setReplacingKey] = useState<string | null>(null)
  const [uploading, setUploading] = useState(false)
  const [batchDeleting, setBatchDeleting] = useState(false)
  const [prefixFilter, setPrefixFilter] = useState('all')
  const [uploadPrefix, setUploadPrefix] = useState('post-covers/')
  const [searchTerm, setSearchTerm] = useState('')
  const [provider, setProvider] = useState<string | null>(null)
  const [bucket, setBucket] = useState<string | null>(null)
  const [uploadFiles, setUploadFiles] = useState<File[]>([])
  const [selectedKeys, setSelectedKeys] = useState<string[]>([])
  const [activeKey, setActiveKey] = useState<string | null>(null)
  const [metadataForm, setMetadataForm] = useState<MediaMetadataFormState>(defaultMetadataForm)
  const [metadataSaving, setMetadataSaving] = useState(false)
  const [compressBeforeUpload, setCompressBeforeUpload] = useState(true)
  const [compressQuality, setCompressQuality] = useState('0.82')

  const loadItems = useCallback(async (showToast = false) => {
    try {
      if (showToast) {
        setRefreshing(true)
      }
      const prefix = prefixFilter === 'all' ? undefined : prefixFilter
      const result = await adminApi.listMediaObjects({ prefix, limit: 200 })
      const normalizedItems = result.items.map(normalizeMediaItem)
      startTransition(() => {
        setItems(normalizedItems)
        setProvider(result.provider)
        setBucket(result.bucket)
      })
      if (showToast) {
        toast.success('媒体对象列表已刷新。')
      }
    } catch (error) {
      toast.error(error instanceof ApiError ? error.message : '媒体对象列表加载失败。')
    } finally {
      setLoading(false)
      setRefreshing(false)
    }
  }, [prefixFilter])

  useEffect(() => {
    void loadItems(false)
  }, [loadItems])

  useEffect(() => {
    setSelectedKeys((current) =>
      current.filter((key) => items.some((item) => item.key === key)),
    )
  }, [items])

  useEffect(() => {
    if (!items.length) {
      setActiveKey(null)
      setMetadataForm(defaultMetadataForm)
      return
    }

    setActiveKey((current) => (current && items.some((item) => item.key === current) ? current : items[0].key))
  }, [items])

  const activeItem = useMemo(
    () => items.find((item) => item.key === activeKey) ?? null,
    [activeKey, items],
  )

  useEffect(() => {
    setMetadataForm(toMetadataForm(activeItem))
  }, [activeItem])

  const filteredItems = useMemo(() => {
    const keyword = searchTerm.trim().toLowerCase()
    if (!keyword) {
      return items
    }
    return items.filter((item) => item.key.toLowerCase().includes(keyword))
  }, [items, searchTerm])

  const allFilteredSelected =
    filteredItems.length > 0 && filteredItems.every((item) => selectedKeys.includes(item.key))

  async function prepareFiles(files: File[], targetPrefix = uploadPrefix) {
    if (!compressBeforeUpload) {
      return files
    }

    const quality = Number.parseFloat(compressQuality)
    const safeQuality = Number.isFinite(quality) ? Math.min(Math.max(quality, 0.4), 0.95) : 0.82
    const normalizeCover =
      targetPrefix === 'post-covers/' || targetPrefix === 'review-covers/'

    const result: File[] = []
    for (const file of files) {
      const compressed = normalizeCover
        ? await normalizeCoverImageWithPrompt(file, {
            quality: safeQuality,
            ask: true,
            contextLabel: `封面规范化上传（${file.name}）`,
          })
        : await maybeCompressImageWithPrompt(file, {
            quality: safeQuality,
            ask: true,
            contextLabel: `媒体库上传（${file.name}）`,
          })
      if (compressed.preview) {
        toast.message(formatCompressionPreview(compressed.preview) || '压缩评估完成')
      }
      result.push(compressed.file)
    }
    return result
  }

  return (
    <div className="space-y-6">
      <div className="flex flex-col gap-4 xl:flex-row xl:items-end xl:justify-between">
        <div className="space-y-3">
          <Badge variant="secondary">媒体库</Badge>
          <div>
            <h2 className="text-3xl font-semibold tracking-tight">对象存储媒体管理</h2>
            <p className="mt-2 max-w-3xl text-sm leading-7 text-muted-foreground">
              查看当前对象存储里的封面资源，支持上传、批量删除、压缩上传和原位替换。
            </p>
          </div>
        </div>

        <div className="flex flex-wrap items-center gap-3">
          <Button variant="outline" onClick={() => void loadItems(true)} disabled={refreshing}>
            <RefreshCcw className="h-4 w-4" />
            {refreshing ? '刷新中...' : '刷新'}
          </Button>
          <Button
            variant="danger"
            disabled={!selectedKeys.length || batchDeleting}
            data-testid="media-batch-delete"
            onClick={async () => {
              if (!window.confirm(`确定批量删除 ${selectedKeys.length} 个对象吗？`)) {
                return
              }

              try {
                setBatchDeleting(true)
                const result = await adminApi.batchDeleteMediaObjects(selectedKeys)
                if (result.failed.length) {
                  toast.warning(`已删除 ${result.deleted.length} 个，失败 ${result.failed.length} 个。`)
                } else {
                  toast.success(`已删除 ${result.deleted.length} 个对象。`)
                }
                await loadItems(false)
              } catch (error) {
                toast.error(error instanceof ApiError ? error.message : '批量删除失败。')
              } finally {
                setBatchDeleting(false)
              }
            }}
          >
            <Trash2 className="h-4 w-4" />
            批量删除 ({selectedKeys.length})
          </Button>
        </div>
      </div>

      <Card>
        <CardHeader>
          <CardTitle>上传与处理</CardTitle>
          <CardDescription>
            Provider：{provider ?? '未配置'} / Bucket：{bucket ?? '未配置'}
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-3">
          <div className="grid gap-3 lg:grid-cols-[220px_220px_1fr]">
            <Select value={prefixFilter} onChange={(event) => setPrefixFilter(event.target.value)}>
              <option value="all">全部目录</option>
              <option value="post-covers/">文章封面</option>
              <option value="review-covers/">评测封面</option>
              <option value="uploads/">通用上传</option>
            </Select>
            <Select value={uploadPrefix} onChange={(event) => setUploadPrefix(event.target.value)}>
              <option value="post-covers/">上传到文章封面</option>
              <option value="review-covers/">上传到评测封面</option>
              <option value="uploads/">上传到通用目录</option>
            </Select>
            <Input
              data-testid="media-search"
              placeholder="按对象 key 搜索"
              value={searchTerm}
              onChange={(event) => setSearchTerm(event.target.value)}
            />
          </div>

          <div className="grid gap-3 lg:grid-cols-[1fr_auto_auto_auto]">
            <Input
              data-testid="media-upload-input"
              type="file"
              multiple
              accept="image/*"
              onChange={(event) => {
                const files = Array.from(event.target.files || [])
                setUploadFiles(files)
              }}
            />
            <Button
              type="button"
              variant="outline"
              onClick={() => setCompressBeforeUpload((current) => !current)}
            >
              {compressBeforeUpload ? <CheckSquare className="h-4 w-4" /> : <Square className="h-4 w-4" />}
              压缩上传
            </Button>
            <Input
              className="w-[96px]"
              value={compressQuality}
              onChange={(event) => setCompressQuality(event.target.value)}
              placeholder="0.82"
              disabled={!compressBeforeUpload}
            />
            <Button
              disabled={!uploadFiles.length || uploading}
              data-testid="media-upload"
              onClick={async () => {
                try {
                  setUploading(true)
                  const files = await prepareFiles(uploadFiles)
                  const result = await adminApi.uploadMediaObjects(files, {
                    prefix: uploadPrefix,
                  })
                  toast.success(`上传完成，共 ${result.uploaded.length} 个文件。`)
                  setUploadFiles([])
                  await loadItems(false)
                } catch (error) {
                  toast.error(error instanceof ApiError ? error.message : '上传失败。')
                } finally {
                  setUploading(false)
                }
              }}
            >
              <Upload className="h-4 w-4" />
              {uploading ? '上传中...' : '上传'}
            </Button>
          </div>
          {uploadFiles.length ? (
            <p className="text-xs text-muted-foreground">
              已选择 {uploadFiles.length} 个文件。
              {uploadPrefix === 'post-covers/' || uploadPrefix === 'review-covers/'
                ? ' 当前会自动裁切为 16:9 封面，并优先转成 AVIF/WebP。'
                : ''}
            </p>
          ) : null}
        </CardContent>
      </Card>

      {activeItem ? (
        <Card>
          <CardHeader>
            <CardTitle>媒体元数据</CardTitle>
            <CardDescription>
              当前编辑：{activeItem.key}。这里维护标题、alt、说明和标签，供文章封面 / 媒体选择器统一复用。
            </CardDescription>
          </CardHeader>
          <CardContent className="space-y-6">
            <div className="grid gap-4 lg:grid-cols-[minmax(0,1.2fr)_minmax(320px,0.8fr)]">
              <div className="space-y-4">
                <div className="grid gap-4 lg:grid-cols-2">
                  <FormField label="标题" hint="媒体资源的人类可读名称。">
                    <Input
                      value={metadataForm.title}
                      onChange={(event) =>
                        setMetadataForm((current) => ({ ...current, title: event.target.value }))
                      }
                      placeholder="文章封面 / 站点横幅"
                    />
                  </FormField>
                  <FormField label="Alt 文本" hint="用于 img alt 和无障碍描述。">
                    <Input
                      value={metadataForm.altText}
                      onChange={(event) =>
                        setMetadataForm((current) => ({ ...current, altText: event.target.value }))
                      }
                      placeholder="夜色下的终端风格博客封面"
                    />
                  </FormField>
                </div>

                <FormField label="标签" hint="多个标签用英文逗号分隔。">
                  <Input
                    value={metadataForm.tags}
                    onChange={(event) =>
                      setMetadataForm((current) => ({ ...current, tags: event.target.value }))
                    }
                    placeholder="cover, astro, terminal"
                  />
                </FormField>

                <FormField label="Caption" hint="适合前台图注、图片说明。">
                  <Textarea
                    value={metadataForm.caption}
                    onChange={(event) =>
                      setMetadataForm((current) => ({ ...current, caption: event.target.value }))
                    }
                    rows={4}
                    placeholder="这张图通常用于文章列表和详情页头图。"
                  />
                </FormField>

                <FormField label="内部备注" hint="仅后台使用，例如素材来源、版权或推荐用途。">
                  <Textarea
                    value={metadataForm.notes}
                    onChange={(event) =>
                      setMetadataForm((current) => ({ ...current, notes: event.target.value }))
                    }
                    rows={4}
                    placeholder="来源：Unsplash / 站点截图 / AI 生成"
                  />
                </FormField>

                <div className="flex flex-wrap items-center gap-3">
                  <Button
                    disabled={metadataSaving}
                    data-testid="media-save-metadata"
                    onClick={async () => {
                      if (!activeItem) {
                        return
                      }

                      try {
                        setMetadataSaving(true)
                        const result = await adminApi.updateMediaObjectMetadata({
                          key: activeItem.key,
                          title: metadataForm.title || null,
                          altText: metadataForm.altText || null,
                          caption: metadataForm.caption || null,
                          tags: parseTagList(metadataForm.tags),
                          notes: metadataForm.notes || null,
                        })
                        startTransition(() => {
                          setItems((current) =>
                            current.map((item) =>
                              item.key === result.key
                                ? {
                                    ...item,
                                    title: result.title,
                                    alt_text: result.alt_text,
                                    caption: result.caption,
                                    tags: normalizeMediaTags(result.tags),
                                    notes: result.notes,
                                  }
                                : item,
                            ),
                          )
                        })
                        toast.success('媒体元数据已保存。')
                      } catch (error) {
                        toast.error(error instanceof ApiError ? error.message : '保存媒体元数据失败。')
                      } finally {
                        setMetadataSaving(false)
                      }
                    }}
                  >
                    <Save className="h-4 w-4" />
                    {metadataSaving ? '保存中...' : '保存元数据'}
                  </Button>
                  <Button variant="outline" onClick={() => setMetadataForm(toMetadataForm(activeItem))}>
                    重置
                  </Button>
                </div>
              </div>

              <div className="space-y-4 rounded-3xl border border-border/70 bg-background/50 p-4">
                <div className="aspect-[16/9] overflow-hidden rounded-2xl border border-border/70 bg-muted/30">
                  <img
                    src={activeItem.url}
                    alt={metadataForm.altText || activeItem.key}
                    className="h-full w-full object-cover"
                  />
                </div>
                <div className="space-y-2 text-sm text-muted-foreground">
                  <p className="break-all font-medium text-foreground">{activeItem.key}</p>
                  <p>{formatBytes(activeItem.size_bytes)} · {activeItem.last_modified ?? '未知修改时间'}</p>
                  <p>{metadataForm.altText || '尚未填写 alt 文本'}</p>
                </div>
              </div>
            </div>
          </CardContent>
        </Card>
      ) : null}

      {loading ? (
        <Skeleton className="h-[520px] rounded-3xl" />
      ) : (
        <div className="grid gap-4 xl:grid-cols-2 2xl:grid-cols-3">
          {filteredItems.map((item, index) => {
            const selected = selectedKeys.includes(item.key)
            const replaceInputId = `replace-media-${index}`
            const itemTags = normalizeMediaTags(item.tags)

            return (
              <Card
                key={item.key}
                data-testid={`media-item-${index}`}
                className={`overflow-hidden ${activeKey === item.key ? 'ring-1 ring-primary/40' : ''}`}
              >
                <div className="relative aspect-[16/9] overflow-hidden bg-muted/30">
                  <img src={item.url} alt={item.key} className="h-full w-full object-cover" />
                  <button
                    type="button"
                    className="absolute left-2 top-2 rounded-xl border border-border/80 bg-background/80 p-1"
                    onClick={() => {
                      setSelectedKeys((current) => {
                        if (current.includes(item.key)) {
                          return current.filter((key) => key !== item.key)
                        }
                        return [...current, item.key]
                      })
                    }}
                  >
                    {selected ? <CheckSquare className="h-4 w-4 text-primary" /> : <Square className="h-4 w-4" />}
                  </button>
                </div>
                <CardContent className="space-y-4 p-5">
                  <div className="space-y-2">
                    <p className="line-clamp-2 break-all text-sm font-medium">{item.key}</p>
                    <div className="flex flex-wrap gap-2 text-xs text-muted-foreground">
                      <span>{formatBytes(item.size_bytes)}</span>
                      {item.last_modified ? <span>{item.last_modified}</span> : null}
                    </div>
                    {item.title ? <p className="text-sm text-foreground">{item.title}</p> : null}
                    {itemTags.length ? (
                      <div className="flex flex-wrap gap-2">
                        {itemTags.slice(0, 4).map((tag) => (
                          <Badge key={`${item.key}-${tag}`} variant="outline">
                            {tag}
                          </Badge>
                        ))}
                      </div>
                    ) : null}
                  </div>
                  <div className="flex flex-wrap gap-2">
                    <Button
                      size="sm"
                      variant="outline"
                      onClick={() => setActiveKey(item.key)}
                      data-testid={`media-edit-${index}`}
                    >
                      元数据
                    </Button>
                    <Button
                      size="sm"
                      variant="outline"
                      onClick={async () => {
                        try {
                          await navigator.clipboard.writeText(item.url)
                          toast.success('图片链接已复制。')
                        } catch {
                          toast.error('复制失败，请手动复制。')
                        }
                      }}
                    >
                      <Copy className="h-4 w-4" />
                      复制链接
                    </Button>
                    <Button size="sm" variant="outline" asChild>
                      <label htmlFor={replaceInputId} className="cursor-pointer">
                        <Replace className="h-4 w-4" />
                        替换
                      </label>
                    </Button>
                    <input
                      id={replaceInputId}
                      data-testid={`media-replace-input-${index}`}
                      className="hidden"
                      type="file"
                      accept="image/*"
                      onChange={async (event) => {
                        const file = event.target.files?.item(0)
                        event.currentTarget.value = ''
                        if (!file) {
                          return
                        }

                        try {
                          setReplacingKey(item.key)
                          const [prepared] = await prepareFiles(
                            [file],
                            item.key.startsWith('review-covers/')
                              ? 'review-covers/'
                              : item.key.startsWith('post-covers/')
                                ? 'post-covers/'
                                : 'uploads/',
                          )
                          const result = await adminApi.replaceMediaObject(item.key, prepared)
                          startTransition(() => {
                            setItems((current) =>
                              current.map((currentItem) =>
                                currentItem.key === item.key
                                  ? { ...currentItem, url: result.url }
                                  : currentItem,
                              ),
                            )
                          })
                          toast.success('已替换媒体对象。')
                        } catch (error) {
                          toast.error(error instanceof ApiError ? error.message : '替换失败。')
                        } finally {
                          setReplacingKey(null)
                        }
                      }}
                    />
                    <Button
                      size="sm"
                      variant="danger"
                      disabled={deletingKey === item.key || replacingKey === item.key}
                      data-testid={`media-delete-${index}`}
                      onClick={async () => {
                        if (!window.confirm(`确定删除 ${item.key} 吗？`)) {
                          return
                        }
                        try {
                          setDeletingKey(item.key)
                          await adminApi.deleteMediaObject(item.key)
                          startTransition(() => {
                            setItems((current) =>
                              current.filter((currentItem) => currentItem.key !== item.key),
                            )
                            setSelectedKeys((current) =>
                              current.filter((key) => key !== item.key),
                            )
                          })
                          toast.success('媒体对象已删除。')
                        } catch (error) {
                          toast.error(error instanceof ApiError ? error.message : '删除媒体对象失败。')
                        } finally {
                          setDeletingKey(null)
                        }
                      }}
                    >
                      <Trash2 className="h-4 w-4" />
                      {deletingKey === item.key
                        ? '删除中...'
                        : replacingKey === item.key
                          ? '替换中...'
                          : '删除'}
                    </Button>
                  </div>
                </CardContent>
              </Card>
            )
          })}

          {!filteredItems.length ? (
            <Card className="xl:col-span-2 2xl:col-span-3">
              <CardContent className="flex flex-col items-center gap-3 px-6 py-16 text-center text-muted-foreground">
                <ImageIcon className="h-8 w-8" />
                <p>当前筛选条件下没有媒体对象。</p>
              </CardContent>
            </Card>
          ) : null}
        </div>
      )}

      {filteredItems.length ? (
        <Card>
          <CardContent className="flex flex-wrap items-center justify-between gap-3 pt-6 text-sm text-muted-foreground">
            <p>
              当前筛选 {filteredItems.length} 个对象，已选择 {selectedKeys.length} 个。
            </p>
            <Button
              variant="outline"
              onClick={() => {
                if (allFilteredSelected) {
                  setSelectedKeys((current) =>
                    current.filter(
                      (key) => !filteredItems.some((item) => item.key === key),
                    ),
                  )
                  return
                }
                setSelectedKeys((current) => {
                  const next = new Set(current)
                  filteredItems.forEach((item) => next.add(item.key))
                  return Array.from(next)
                })
              }}
            >
              {allFilteredSelected ? <Square className="h-4 w-4" /> : <CheckSquare className="h-4 w-4" />}
              {allFilteredSelected ? '取消全选' : '全选当前筛选'}
            </Button>
          </CardContent>
        </Card>
      ) : null}
    </div>
  )
}
