'use client'

import { useState, useRef, useEffect, useMemo } from 'react'
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea'
import { Spinner } from '@/components/ui/spinner'
import { FolderOpen, Trash2, CheckCircle2, XCircle, AlertTriangle } from 'lucide-react'
import Image from 'next/image'
import { uploadToCos } from '@/lib/cos-client'
import { apiFetch } from '@/lib/api'

interface BatchImportDialogProps {
  open: boolean
  onOpenChange: (open: boolean) => void
  onSuccess: () => void
}

type ItemStatus = 'pending' | 'uploading' | 'creating' | 'done' | 'error'

interface ParsedItem {
  id: string // riddle_id 或文件夹名，仅用于 React key
  riddleId?: string
  folderName: string
  // 本地文件引用
  referenceFile?: File
  riddleFile?: File
  // 预览 URL（本地 blob URL）
  referencePreview?: string
  riddlePreview?: string
  // 从 metadata.json 提取
  anchorText: string
  answer: string
  punchline: string
  hint1: string
  hint2: string
  hint3: string
  // 解析警告/错误信息
  parseError?: string
  // 上传/创建过程状态
  status: ItemStatus
  statusMessage?: string
}

interface FileWithPath extends File {
  webkitRelativePath: string
}

interface RiddleMetadata {
  plan?: {
    riddle_id?: string
    anchor_text?: string
    answer?: string
    hints?: string[]
    homophone_explanation?: string
    riddle?: {
      homophone_explanation?: string
    }
  }
}

interface RiddleConfigItem {
  riddle_id?: string
  answer?: string
  hints?: string[]
  homophone_explanation?: string
  anchor_text?: string
  reference_image?: string
  riddle_image?: string
}

function truncate(s: string | undefined, max: number): string {
  if (!s) return ''
  return s.length > max ? s.slice(0, max) : s
}

const ANSWER_MAX_LENGTH = 8

function normalizePath(path: string): string {
  return path.replace(/\\/g, '/').replace(/^\/+/, '').replace(/\/+/g, '/')
}

function joinPath(base: string, path: string): string {
  const cleanPath = normalizePath(path)
  if (!base) return cleanPath
  return normalizePath(`${base}/${cleanPath}`)
}

function getRelativePathWithoutRoot(file: FileWithPath): string {
  const rel = normalizePath(file.webkitRelativePath)
  const [, ...rest] = rel.split('/')
  return rest.join('/')
}

function buildFilePathMap(files: FileList): Map<string, FileWithPath> {
  const fileByPath = new Map<string, FileWithPath>()
  for (let i = 0; i < files.length; i += 1) {
    const file = files[i] as FileWithPath
    const rel = normalizePath(file.webkitRelativePath)
    if (!rel) continue
    fileByPath.set(rel, file)
    const withoutRoot = getRelativePathWithoutRoot(file)
    if (withoutRoot) fileByPath.set(withoutRoot, file)
  }
  return fileByPath
}

function validateItem(item: ParsedItem) {
  if (item.riddleId !== undefined && !item.riddleId) {
    item.parseError = '未解析到 riddle_id'
  } else if (item.riddleId && item.riddleId.length > 128) {
    item.parseError = 'riddle_id 长度不能超过 128'
  } else if (!item.answer) {
    item.parseError = '未解析到 answer'
  } else if (item.answer.length > ANSWER_MAX_LENGTH) {
    item.parseError = `答案超过 ${ANSWER_MAX_LENGTH} 字（${item.answer.length}）`
  } else if (!item.referenceFile || !item.riddleFile) {
    item.parseError = '未匹配到 reference_image 或 riddle_image 对应图片'
  }
}

function markDuplicateRiddleIds(items: ParsedItem[]) {
  const counts = new Map<string, number>()
  items.forEach((item) => {
    if (!item.riddleId) return
    counts.set(item.riddleId, (counts.get(item.riddleId) || 0) + 1)
  })

  items.forEach((item) => {
    if (item.riddleId && counts.get(item.riddleId)! > 1) {
      item.parseError = `riddle_id 重复: ${item.riddleId}`
    }
  })
}

async function parseConfigJson(
  jsonFile: FileWithPath,
  fileByPath: Map<string, FileWithPath>
): Promise<ParsedItem[]> {
  const text = await jsonFile.text()
  const json = JSON.parse(text) as unknown
  if (!Array.isArray(json)) return []

  const jsonPath = normalizePath(jsonFile.webkitRelativePath)
  const jsonDir = jsonPath.split('/').slice(0, -1).join('/')
  const items: ParsedItem[] = []

  json.forEach((raw, index) => {
    const config = raw as RiddleConfigItem
    if (!config || typeof config !== 'object') return

    const riddleId = (config.riddle_id || '').trim()
    const referencePath = config.reference_image || ''
    const riddlePath = config.riddle_image || ''
    const reference = fileByPath.get(joinPath(jsonDir, referencePath)) || fileByPath.get(normalizePath(referencePath))
    const riddle = fileByPath.get(joinPath(jsonDir, riddlePath)) || fileByPath.get(normalizePath(riddlePath))
    const id = `${riddleId || jsonFile.name}-${index + 1}`
    const folderName = riddleId || referencePath.split('/').slice(-2, -1)[0] || `第 ${index + 1} 关`
    const hints = Array.isArray(config.hints) ? config.hints : []

    const item: ParsedItem = {
      id,
      riddleId,
      folderName,
      referenceFile: reference,
      riddleFile: riddle,
      referencePreview: reference ? URL.createObjectURL(reference) : undefined,
      riddlePreview: riddle ? URL.createObjectURL(riddle) : undefined,
      anchorText: truncate(config.anchor_text, 500),
      answer: (config.answer || '').trim(),
      punchline: (config.homophone_explanation || '').trim(),
      hint1: (hints[0] || '').trim(),
      hint2: (hints[1] || '').trim(),
      hint3: (hints[2] || '').trim(),
      status: 'pending',
    }

    validateItem(item)
    items.push(item)
  })

  markDuplicateRiddleIds(items)

  return items
}

async function parseFolders(files: FileList): Promise<ParsedItem[]> {
  const fileByPath = buildFilePathMap(files)
  const jsonFiles = Array.from(fileByPath.values()).filter(
    (file, index, arr) =>
      file.name.toLowerCase().endsWith('.json') && arr.indexOf(file) === index
  )
  for (const jsonFile of jsonFiles) {
    try {
      const configItems = await parseConfigJson(jsonFile, fileByPath)
      if (configItems.length > 0) {
        return configItems
      }
    } catch {
      // Not the new array-based config. Fall back to legacy per-folder metadata.
    }
  }

  // 按第一层目录名分组
  const groups = new Map<string, FileWithPath[]>()
  for (let i = 0; i < files.length; i += 1) {
    const f = files[i] as FileWithPath
    const rel = f.webkitRelativePath
    if (!rel) continue
    const parts = rel.split('/')
    if (parts.length < 2) continue
    // 用倒数第二层作为关卡目录名：e.g. riddles/68f1.../metadata.json
    // 但通常用户选的根目录就是 riddles，第一层就是关卡目录
    // 我们取「包含 metadata.json 的那个目录」作为分组 key
    const folderKey = parts.slice(0, -1).join('/')
    if (!groups.has(folderKey)) groups.set(folderKey, [])
    groups.get(folderKey)!.push(f)
  }

  const items: ParsedItem[] = []

  for (const [folderKey, groupFiles] of Array.from(groups.entries())) {
    const metadata = groupFiles.find((f) => f.name === 'metadata.json')
    const reference = groupFiles.find((f) => f.name === 'reference.webp')
    const riddle = groupFiles.find((f) => f.name === 'riddle.webp')
    // 只有同时存在 metadata.json + reference.webp + riddle.webp 的目录才视为关卡
    if (!metadata || !reference || !riddle) continue

    const folderName = folderKey.split('/').pop() || folderKey

    const item: ParsedItem = {
      id: folderKey,
      folderName,
      referenceFile: reference,
      riddleFile: riddle,
      referencePreview: URL.createObjectURL(reference),
      riddlePreview: URL.createObjectURL(riddle),
      anchorText: '',
      answer: '',
      punchline: '',
      hint1: '',
      hint2: '',
      hint3: '',
      status: 'pending',
    }

    try {
      const text = await metadata.text()
      const json = JSON.parse(text) as RiddleMetadata
      const plan = json.plan || {}
      item.riddleId = (plan.riddle_id || '').trim() || undefined
      item.anchorText = truncate(plan.anchor_text, 500)
      item.answer = (plan.answer || '').trim()
      item.punchline = (
        plan.homophone_explanation ||
        plan.riddle?.homophone_explanation ||
        ''
      ).trim()
      const hints = Array.isArray(plan.hints) ? plan.hints : []
      item.hint1 = (hints[0] || '').trim()
      item.hint2 = (hints[1] || '').trim()
      item.hint3 = (hints[2] || '').trim()

      validateItem(item)
    } catch (e) {
      item.parseError = `解析 metadata.json 失败: ${
        e instanceof Error ? e.message : String(e)
      }`
    }

    items.push(item)
  }

  // 稳定排序：按文件夹名
  items.sort((a, b) => a.folderName.localeCompare(b.folderName))
  return items
}

export function BatchImportDialog({
  open,
  onOpenChange,
  onSuccess,
}: BatchImportDialogProps) {
  const folderInputRef = useRef<HTMLInputElement>(null)
  const [items, setItems] = useState<ParsedItem[]>([])
  const [isParsing, setIsParsing] = useState(false)
  const [isRunning, setIsRunning] = useState(false)
  const [globalError, setGlobalError] = useState('')

  // 关闭后清理预览 URL 并重置
  useEffect(() => {
    if (!open) {
      items.forEach((it) => {
        if (it.referencePreview) URL.revokeObjectURL(it.referencePreview)
        if (it.riddlePreview) URL.revokeObjectURL(it.riddlePreview)
      })
      setItems([])
      setGlobalError('')
      setIsRunning(false)
      setIsParsing(false)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open])

  const stats = useMemo(() => {
    const total = items.length
    const done = items.filter((i) => i.status === 'done').length
    const error = items.filter((i) => i.status === 'error').length
    const invalid = items.filter((i) => i.parseError).length
    return { total, done, error, invalid }
  }, [items])

  const canSubmit =
    items.length > 0 &&
    !isRunning &&
    items.some((i) => i.status !== 'done' && !i.parseError)

  const handleSelectFolder = () => {
    folderInputRef.current?.click()
  }

  const handleFolderChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = e.target.files
    if (!files || files.length === 0) return
    setIsParsing(true)
    setGlobalError('')
    try {
      const parsed = await parseFolders(files)
      if (parsed.length === 0) {
        setGlobalError(
          '未在所选目录中找到任何有效关卡。请选择包含 riddles.json 和 riddle_images 的目录，或旧格式的 metadata.json、reference.webp、riddle.webp 子目录'
        )
      }
      setItems(parsed)
    } catch (err) {
      setGlobalError(
        err instanceof Error ? err.message : '解析失败'
      )
    } finally {
      setIsParsing(false)
      // 允许重新选择同一目录
      if (folderInputRef.current) folderInputRef.current.value = ''
    }
  }

  const updateItem = (id: string, patch: Partial<ParsedItem>) => {
    setItems((prev) =>
      prev.map((it) => {
        if (it.id !== id) return it
        const next = { ...it, ...patch }
        // 每次编辑后重新评估答案的校验
        if (patch.answer !== undefined) {
          if (!next.answer) next.parseError = '未填写答案'
          else if (next.answer.length > ANSWER_MAX_LENGTH)
            next.parseError = `答案超过 ${ANSWER_MAX_LENGTH} 字（${next.answer.length}）`
          else next.parseError = undefined
        }
        return next
      })
    )
  }

  const removeItem = (id: string) => {
    setItems((prev) => {
      const target = prev.find((i) => i.id === id)
      if (target) {
        if (target.referencePreview) URL.revokeObjectURL(target.referencePreview)
        if (target.riddlePreview) URL.revokeObjectURL(target.riddlePreview)
      }
      return prev.filter((i) => i.id !== id)
    })
  }

  const handleRun = async () => {
    setIsRunning(true)
    setGlobalError('')

    // 使用函数式 setItems 取到最新列表
    const snapshot = items
    for (const it of snapshot) {
      if (it.status === 'done') continue
      if (it.parseError) continue
      if (!it.referenceFile || !it.riddleFile) continue

      try {
        updateItem(it.id, { status: 'uploading', statusMessage: '上传图片…' })

        const [image1Url, image2Url] = await Promise.all([
          uploadToCos(it.referenceFile, it.referenceFile.name),
          uploadToCos(it.riddleFile, it.riddleFile.name),
        ])

        updateItem(it.id, { status: 'creating', statusMessage: '导入关卡…' })

        const payload = {
          ...(it.riddleId ? { riddleId: it.riddleId } : {}),
          image1Url,
          image1Description: it.anchorText || '',
          image2Url,
          image2Description: '',
          answer: it.answer,
          punchline: it.punchline || '',
          hint1: it.hint1 || '',
          hint2: it.hint2 || '',
          hint3: it.hint3 || '',
        }

        const res = await apiFetch('/api/levels', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload),
        })
        if (!res.ok) {
          const err = await res.json().catch(() => ({}))
          throw new Error(err.error || `HTTP ${res.status}`)
        }
        updateItem(it.id, { status: 'done', statusMessage: '已导入' })
      } catch (e) {
        updateItem(it.id, {
          status: 'error',
          statusMessage: e instanceof Error ? e.message : '失败',
        })
      }
    }

    setIsRunning(false)
    // 触发列表刷新
    onSuccess()
  }

  return (
    <Dialog open={open} onOpenChange={onOpenChange}>
      <DialogContent className="sm:max-w-5xl max-h-[90vh] overflow-hidden flex flex-col">
        <DialogHeader>
          <DialogTitle>批量导入关卡</DialogTitle>
          <DialogDescription>
            选择包含 riddles.json 和 riddle_images 的目录；也兼容旧格式的多个关卡子文件夹。
          </DialogDescription>
        </DialogHeader>

        {globalError && (
          <div className="p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded-md">
            {globalError}
          </div>
        )}

        <div className="flex items-center gap-3">
          <Button
            type="button"
            variant="outline"
            onClick={handleSelectFolder}
            disabled={isParsing || isRunning}
          >
            <FolderOpen className="h-4 w-4 mr-2" />
            {items.length > 0 ? '重新选择目录' : '选择目录'}
          </Button>
          {isParsing && (
            <span className="flex items-center gap-2 text-sm text-muted-foreground">
              <Spinner size="sm" /> 解析中…
            </span>
          )}
          {items.length > 0 && (
            <span className="text-sm text-muted-foreground">
              共 {stats.total} 个关卡 · 成功 {stats.done} · 失败 {stats.error}
              {stats.invalid > 0 && ` · 待修正 ${stats.invalid}`}
            </span>
          )}

          {/* 隐藏的文件夹选择 input：webkitdirectory 让浏览器选择目录 */}
          <input
            ref={folderInputRef}
            type="file"
            className="hidden"
            multiple
            onChange={handleFolderChange}
            {...({
              webkitdirectory: '',
              directory: '',
            } as Record<string, string>)}
          />
        </div>

        <div className="flex-1 overflow-y-auto -mx-6 px-6">
          {items.length === 0 ? (
            <div className="text-center py-16 text-muted-foreground text-sm">
              {isParsing ? '解析中…' : '尚未选择目录'}
            </div>
          ) : (
            <div className="space-y-4">
              {items.map((it, idx) => (
                <ItemCard
                  key={it.id}
                  index={idx + 1}
                  item={it}
                  disabled={isRunning}
                  onChange={(patch) => updateItem(it.id, patch)}
                  onRemove={() => removeItem(it.id)}
                />
              ))}
            </div>
          )}
        </div>

        <DialogFooter>
          <Button
            type="button"
            variant="outline"
            onClick={() => onOpenChange(false)}
            disabled={isRunning}
          >
            {stats.done > 0 ? '关闭' : '取消'}
          </Button>
          <Button type="button" onClick={handleRun} disabled={!canSubmit}>
            {isRunning ? (
              <>
                <Spinner size="sm" className="mr-2" />
                导入中…
              </>
            ) : (
              `确认导入 (${items.filter((i) => i.status !== 'done' && !i.parseError).length})`
            )}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}

interface ItemCardProps {
  index: number
  item: ParsedItem
  disabled: boolean
  onChange: (patch: Partial<ParsedItem>) => void
  onRemove: () => void
}

function ItemCard({ index, item, disabled, onChange, onRemove }: ItemCardProps) {
  const statusNode = (() => {
    switch (item.status) {
      case 'uploading':
      case 'creating':
        return (
          <span className="flex items-center gap-1 text-blue-600 text-xs">
            <Spinner size="sm" /> {item.statusMessage}
          </span>
        )
      case 'done':
        return (
          <span className="flex items-center gap-1 text-green-600 text-xs">
            <CheckCircle2 className="h-4 w-4" /> {item.statusMessage || '已导入'}
          </span>
        )
      case 'error':
        return (
          <span className="flex items-center gap-1 text-red-600 text-xs">
            <XCircle className="h-4 w-4" /> {item.statusMessage}
          </span>
        )
      default:
        if (item.parseError) {
          return (
            <span className="flex items-center gap-1 text-amber-600 text-xs">
              <AlertTriangle className="h-4 w-4" /> {item.parseError}
            </span>
          )
        }
        return (
          <span className="text-muted-foreground text-xs">待导入</span>
        )
    }
  })()

  return (
    <div className="border rounded-lg p-4 space-y-3 bg-card">
      <div className="flex items-center justify-between gap-2">
        <div className="flex items-center gap-3 min-w-0">
          <span className="text-sm font-medium text-muted-foreground">
            #{index}
          </span>
          <span className="text-sm truncate" title={item.folderName}>
            {item.folderName}
          </span>
          {statusNode}
        </div>
        <Button
          type="button"
          variant="ghost"
          size="icon"
          className="h-8 w-8 text-red-600 hover:bg-red-50"
          onClick={onRemove}
          disabled={disabled || item.status === 'done'}
        >
          <Trash2 className="h-4 w-4" />
        </Button>
      </div>

      <div className="grid grid-cols-[auto,1fr] gap-4">
        {/* 左侧：图片预览 */}
        <div className="flex gap-2">
          <div className="space-y-1">
            <div className="relative w-32 h-32 rounded border bg-gray-50 overflow-hidden">
              {item.referencePreview && (
                <Image
                  src={item.referencePreview}
                  alt="图片1"
                  fill
                  className="object-contain"
                  sizes="128px"
                  unoptimized
                />
              )}
            </div>
            <p className="text-[11px] text-center text-muted-foreground">图1 · reference</p>
          </div>
          <div className="space-y-1">
            <div className="relative w-32 h-32 rounded border bg-gray-50 overflow-hidden">
              {item.riddlePreview && (
                <Image
                  src={item.riddlePreview}
                  alt="图片2"
                  fill
                  className="object-contain"
                  sizes="128px"
                  unoptimized
                />
              )}
            </div>
            <p className="text-[11px] text-center text-muted-foreground">图2 · riddle</p>
          </div>
        </div>

        {/* 右侧：可编辑字段 */}
        <div className="grid grid-cols-2 gap-3">
          <div className="col-span-2 space-y-1">
            <Label className="text-xs">图片1 描述（anchor_text）</Label>
            <Textarea
              value={item.anchorText}
              onChange={(e) => onChange({ anchorText: e.target.value })}
              rows={2}
              disabled={disabled}
            />
          </div>

          <div className="space-y-1">
            <Label className="text-xs">答案 *</Label>
            <Input
              value={item.answer}
              onChange={(e) => onChange({ answer: e.target.value })}
              maxLength={ANSWER_MAX_LENGTH}
              disabled={disabled}
            />
          </div>

          <div className="space-y-1">
            <Label className="text-xs">谐音梗说明</Label>
            <Input
              value={item.punchline}
              onChange={(e) => onChange({ punchline: e.target.value })}
              disabled={disabled}
            />
          </div>

          <div className="col-span-2 grid grid-cols-3 gap-2">
            <div className="space-y-1">
              <Label className="text-xs">提示 1</Label>
              <Input
                value={item.hint1}
                onChange={(e) => onChange({ hint1: e.target.value })}
                disabled={disabled}
              />
            </div>
            <div className="space-y-1">
              <Label className="text-xs">提示 2</Label>
              <Input
                value={item.hint2}
                onChange={(e) => onChange({ hint2: e.target.value })}
                disabled={disabled}
              />
            </div>
            <div className="space-y-1">
              <Label className="text-xs">提示 3</Label>
              <Input
                value={item.hint3}
                onChange={(e) => onChange({ hint3: e.target.value })}
                disabled={disabled}
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}
