import {
  Ban,
  Bot,
  CheckCheck,
  MessageSquareText,
  RefreshCcw,
  Shield,
  ShieldOff,
  Trash2,
  XCircle,
} 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 {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table'
import { adminApi, ApiError } from '@/lib/api'
import { formatBrowserName, formatCommentScope, formatDateTime } from '@/lib/admin-format'
import type {
  CommentBlacklistRecord,
  CommentPersonaAnalysisLogRecord,
  CommentPersonaAnalysisResponse,
  CommentRecord,
} from '@/lib/types'

type MatcherType = 'ip' | 'email' | 'user_agent'

type PersonaItem = {
  matcherValue: string
  total: number
  approved: number
  pending: number
  firstAt: string
  latestAt: string
  latestPostSlug: string
}

function moderationBadgeVariant(approved: boolean | null) {
  if (approved) {
    return 'success' as const
  }

  return 'warning' as const
}

function normalizeMatcherValue(type: MatcherType, value: string | null | undefined) {
  const normalized = (value ?? '').trim()
  if (!normalized) {
    return ''
  }

  if (type === 'email') {
    return normalized.toLowerCase()
  }

  return normalized
}

function extractMatcherValue(comment: CommentRecord, type: MatcherType) {
  if (type === 'ip') {
    return normalizeMatcherValue(type, comment.ip_address)
  }
  if (type === 'email') {
    return normalizeMatcherValue(type, comment.email)
  }
  return normalizeMatcherValue(type, comment.user_agent)
}

function matcherLabel(type: MatcherType) {
  if (type === 'ip') return 'IP'
  if (type === 'email') return '邮箱'
  return '浏览器 UA'
}

function toPersonaAnalysisFromLog(
  item: CommentPersonaAnalysisLogRecord,
): CommentPersonaAnalysisResponse {
  return {
    matcher_type: item.matcher_type,
    matcher_value: item.matcher_value,
    total_comments: item.total_comments,
    pending_comments: item.pending_comments,
    first_seen_at: item.from_at,
    latest_seen_at: item.to_at,
    distinct_posts: item.distinct_posts,
    analysis: item.analysis,
    samples: item.samples,
  }
}

export function CommentsPage() {
  const [comments, setComments] = useState<CommentRecord[]>([])
  const [blacklist, setBlacklist] = useState<CommentBlacklistRecord[]>([])
  const [loading, setLoading] = useState(true)
  const [refreshing, setRefreshing] = useState(false)
  const [actingId, setActingId] = useState<number | null>(null)
  const [actingBlacklistId, setActingBlacklistId] = useState<number | null>(null)
  const [banLoadingKey, setBanLoadingKey] = useState<string | null>(null)
  const [searchTerm, setSearchTerm] = useState('')
  const [approvalFilter, setApprovalFilter] = useState('pending')
  const [scopeFilter, setScopeFilter] = useState('all')
  const [profileType, setProfileType] = useState<MatcherType>('ip')
  const [selectedProfileValue, setSelectedProfileValue] = useState('')
  const [manualMatcherType, setManualMatcherType] = useState<MatcherType>('ip')
  const [manualMatcherValue, setManualMatcherValue] = useState('')
  const [manualReason, setManualReason] = useState('')
  const [analyzingPersona, setAnalyzingPersona] = useState(false)
  const [personaAnalysis, setPersonaAnalysis] =
    useState<CommentPersonaAnalysisResponse | null>(null)
  const [personaLogs, setPersonaLogs] = useState<CommentPersonaAnalysisLogRecord[]>([])
  const [personaLogsLoading, setPersonaLogsLoading] = useState(false)

  const loadComments = useCallback(async (showToast = false) => {
    try {
      if (showToast) {
        setRefreshing(true)
      }

      const [nextComments, nextBlacklist] = await Promise.all([
        adminApi.listComments(),
        adminApi.listCommentBlacklist(),
      ])
      startTransition(() => {
        setComments(nextComments)
        setBlacklist(nextBlacklist)
      })

      if (showToast) {
        toast.success('评论列表已刷新。')
      }
    } catch (error) {
      if (error instanceof ApiError && error.status === 401) {
        return
      }
      toast.error(error instanceof ApiError ? error.message : '无法加载评论列表。')
    } finally {
      setLoading(false)
      setRefreshing(false)
    }
  }, [])

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

  const filteredComments = useMemo(() => {
    return comments.filter((comment) => {
      const matchesSearch =
        !searchTerm ||
        [
          comment.author ?? '',
          comment.email ?? '',
          comment.post_slug ?? '',
          comment.content ?? '',
          comment.paragraph_excerpt ?? '',
          comment.paragraph_key ?? '',
          comment.ip_address ?? '',
          comment.user_agent ?? '',
          comment.referer ?? '',
        ]
          .join('\n')
          .toLowerCase()
          .includes(searchTerm.toLowerCase())

      const matchesApproval =
        approvalFilter === 'all' ||
        (approvalFilter === 'approved' && Boolean(comment.approved)) ||
        (approvalFilter === 'pending' && !comment.approved)

      const matchesScope = scopeFilter === 'all' || comment.scope === scopeFilter

      return matchesSearch && matchesApproval && matchesScope
    })
  }, [approvalFilter, comments, scopeFilter, searchTerm])

  const blacklistedMatcherSet = useMemo(() => {
    return new Set(
      blacklist
        .filter((item) => item.effective)
        .map(
          (item) =>
            `${item.matcher_type}:${normalizeMatcherValue(
              item.matcher_type as MatcherType,
              item.matcher_value,
            )}`,
        ),
    )
  }, [blacklist])

  const pendingCount = useMemo(
    () => comments.filter((comment) => !comment.approved).length,
    [comments],
  )

  const paragraphCount = useMemo(
    () => comments.filter((comment) => comment.scope === 'paragraph').length,
    [comments],
  )

  const personas = useMemo(() => {
    const map = new Map<string, PersonaItem>()

    filteredComments.forEach((comment) => {
      const matcherValue = extractMatcherValue(comment, profileType)
      if (!matcherValue) {
        return
      }

      const existing = map.get(matcherValue)
      if (existing) {
        existing.total += 1
        if (comment.approved) {
          existing.approved += 1
        } else {
          existing.pending += 1
        }
        if (comment.created_at < existing.firstAt) {
          existing.firstAt = comment.created_at
        }
        if (comment.created_at > existing.latestAt) {
          existing.latestAt = comment.created_at
          existing.latestPostSlug = comment.post_slug ?? existing.latestPostSlug
        }
        return
      }

      map.set(matcherValue, {
        matcherValue,
        total: 1,
        approved: comment.approved ? 1 : 0,
        pending: comment.approved ? 0 : 1,
        firstAt: comment.created_at,
        latestAt: comment.created_at,
        latestPostSlug: comment.post_slug ?? 'unknown-post',
      })
    })

    return Array.from(map.values()).sort((left, right) => {
      if (right.total !== left.total) {
        return right.total - left.total
      }
      return right.latestAt.localeCompare(left.latestAt)
    })
  }, [filteredComments, profileType])

  useEffect(() => {
    if (!personas.length) {
      setSelectedProfileValue('')
      return
    }

    if (!personas.some((item) => item.matcherValue === selectedProfileValue)) {
      setSelectedProfileValue(personas[0].matcherValue)
    }
  }, [personas, selectedProfileValue])

  const selectedPersona = useMemo(
    () => personas.find((item) => item.matcherValue === selectedProfileValue) ?? null,
    [personas, selectedProfileValue],
  )

  const selectedPersonaComments = useMemo(() => {
    if (!selectedProfileValue) {
      return []
    }

    return filteredComments
      .filter(
        (comment) =>
          extractMatcherValue(comment, profileType) ===
          normalizeMatcherValue(profileType, selectedProfileValue),
      )
      .sort((left, right) => right.created_at.localeCompare(left.created_at))
  }, [filteredComments, profileType, selectedProfileValue])

  useEffect(() => {
    setPersonaAnalysis(null)
  }, [profileType, selectedProfileValue])

  const loadPersonaLogs = useCallback(
    async (type: MatcherType, matcherValue?: string) => {
      try {
        setPersonaLogsLoading(true)
        const items = await adminApi.listCommentPersonaAnalysisLogs({
          matcher_type: type,
          matcher_value: matcherValue,
          limit: 20,
        })

        startTransition(() => {
          setPersonaLogs(items)
        })
      } catch (error) {
        if (error instanceof ApiError && error.status === 401) {
          return
        }

        toast.error(error instanceof ApiError ? error.message : '加载分析历史失败。')
      } finally {
        setPersonaLogsLoading(false)
      }
    },
    [],
  )

  useEffect(() => {
    void loadPersonaLogs(profileType, selectedProfileValue || undefined)
  }, [loadPersonaLogs, profileType, selectedProfileValue])

  const createBlacklist = useCallback(
    async (type: MatcherType, matcherValue: string, reason?: string) => {
      const normalizedValue = normalizeMatcherValue(type, matcherValue)
      if (!normalizedValue) {
        return
      }

      const key = `${type}:${normalizedValue}`
      if (blacklistedMatcherSet.has(key)) {
        toast.info('该规则已在黑名单中。')
        return
      }

      try {
        setBanLoadingKey(key)
        await adminApi.createCommentBlacklist({
          matcher_type: type,
          matcher_value: normalizedValue,
          reason: reason || '后台快速封禁',
          active: true,
        })
        toast.success(`已加入黑名单：${matcherLabel(type)} ${normalizedValue}`)
        await loadComments(false)
      } catch (error) {
        toast.error(error instanceof ApiError ? error.message : '创建黑名单失败。')
      } finally {
        setBanLoadingKey(null)
      }
    },
    [blacklistedMatcherSet, loadComments],
  )

  const analyzeSelectedPersona = useCallback(async () => {
    if (!selectedProfileValue) {
      toast.error('请先选择一个画像来源。')
      return
    }

    try {
      setAnalyzingPersona(true)
      const result = await adminApi.analyzeCommentPersona({
        matcher_type: profileType,
        matcher_value: selectedProfileValue,
        limit: 24,
      })
      startTransition(() => {
        setPersonaAnalysis(result)
      })
      await loadPersonaLogs(profileType, selectedProfileValue)
      toast.success('AI 画像分析完成。')
    } catch (error) {
      toast.error(error instanceof ApiError ? error.message : 'AI 画像分析失败。')
    } finally {
      setAnalyzingPersona(false)
    }
  }, [loadPersonaLogs, profileType, selectedProfileValue])

  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>

        <Button variant="secondary" onClick={() => void loadComments(true)} disabled={refreshing}>
          <RefreshCcw className="h-4 w-4" />
          {refreshing ? '刷新中...' : '刷新'}
        </Button>
      </div>

      <div className="grid gap-4 md:grid-cols-3">
        <Card className="bg-gradient-to-br from-card via-card to-background/70">
          <CardContent className="pt-6">
            <p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">待审核</p>
            <div className="mt-3 text-3xl font-semibold">{pendingCount}</div>
            <p className="mt-2 text-sm text-muted-foreground">需要人工审核处理。</p>
          </CardContent>
        </Card>
        <Card className="bg-gradient-to-br from-card via-card to-background/70">
          <CardContent className="pt-6">
            <p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
              段落评论
            </p>
            <div className="mt-3 text-3xl font-semibold">{paragraphCount}</div>
            <p className="mt-2 text-sm text-muted-foreground">挂载到具体段落锚点。</p>
          </CardContent>
        </Card>
        <Card className="bg-gradient-to-br from-card via-card to-background/70">
          <CardContent className="pt-6">
            <p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">总数</p>
            <div className="mt-3 text-3xl font-semibold">{comments.length}</div>
            <p className="mt-2 text-sm text-muted-foreground">当前系统中全部评论。</p>
          </CardContent>
        </Card>
      </div>

      <Card>
        <CardHeader>
          <CardTitle>评论列表</CardTitle>
          <CardDescription>
            先筛选，再直接通过、隐藏、删除，或快速把来源加入黑名单。
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-4">
          <div className="grid gap-3 lg:grid-cols-[1.2fr_0.6fr_0.6fr]">
            <Input
              placeholder="按作者、邮箱、IP、浏览器、文章 slug 或评论内容搜索"
              value={searchTerm}
              onChange={(event) => setSearchTerm(event.target.value)}
            />
            <Select
              value={approvalFilter}
              onChange={(event) => setApprovalFilter(event.target.value)}
            >
              <option value="all">全部状态</option>
              <option value="pending">仅看待审核</option>
              <option value="approved">仅看已通过</option>
            </Select>
            <Select value={scopeFilter} onChange={(event) => setScopeFilter(event.target.value)}>
              <option value="all">全部范围</option>
              <option value="article">全文</option>
              <option value="paragraph">段落</option>
            </Select>
          </div>

          {loading ? (
            <Skeleton className="h-[680px] rounded-3xl" />
          ) : (
            <div className="grid gap-4 2xl:grid-cols-[1.35fr_1fr]">
              <div className="overflow-auto rounded-2xl border border-border/70">
                <Table>
                  <TableHeader>
                    <TableRow>
                      <TableHead>评论内容</TableHead>
                      <TableHead>状态</TableHead>
                      <TableHead>上下文</TableHead>
                      <TableHead>操作</TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {filteredComments.map((comment) => (
                      <TableRow key={comment.id}>
                        <TableCell>
                          <div className="space-y-2">
                            <div className="flex flex-wrap items-center gap-2">
                              <span className="font-medium">{comment.author ?? '匿名用户'}</span>
                              <Badge variant="outline">{formatCommentScope(comment.scope)}</Badge>
                              <span className="text-xs text-muted-foreground">
                                {formatDateTime(comment.created_at)}
                              </span>
                            </div>
                            <p className="text-sm leading-6 text-muted-foreground">
                              {comment.content ?? '暂无评论内容。'}
                            </p>
                            {comment.scope === 'paragraph' ? (
                              <div className="rounded-2xl border border-border/70 bg-background/60 px-3 py-2 text-xs text-muted-foreground">
                                <p className="font-mono">{comment.paragraph_key ?? '缺少段落键'}</p>
                                <p className="mt-1">
                                  {comment.paragraph_excerpt ?? '没有保存段落摘录。'}
                                </p>
                              </div>
                            ) : null}
                          </div>
                        </TableCell>
                        <TableCell>
                          <Badge variant={moderationBadgeVariant(comment.approved)}>
                            {comment.approved ? '已通过' : '待审核'}
                          </Badge>
                        </TableCell>
                        <TableCell>
                          <div className="space-y-1 text-sm text-muted-foreground">
                            <p className="font-mono text-xs">{comment.post_slug ?? '未知文章'}</p>
                            <p className="font-mono text-xs">
                              {comment.ip_address ? `IP ${comment.ip_address}` : 'IP 未记录'}
                            </p>
                            <p
                              className="max-w-[280px] truncate"
                              title={comment.user_agent ?? undefined}
                            >
                              浏览器：{formatBrowserName(comment.user_agent)}
                            </p>
                            {comment.referer ? (
                              <p className="max-w-[280px] truncate" title={comment.referer}>
                                来源：{comment.referer}
                              </p>
                            ) : null}
                            {comment.email ? <p>邮箱：{comment.email}</p> : null}
                            <div className="flex flex-wrap gap-2 pt-1">
                              {comment.ip_address ? (
                                <Button
                                  size="sm"
                                  variant="outline"
                                  disabled={
                                    banLoadingKey ===
                                    `ip:${normalizeMatcherValue('ip', comment.ip_address)}`
                                  }
                                  onClick={() =>
                                    void createBlacklist(
                                      'ip',
                                      comment.ip_address ?? '',
                                      `评论#${comment.id} 快速封禁`,
                                    )
                                  }
                                >
                                  <Ban className="h-3.5 w-3.5" />
                                  封 IP
                                </Button>
                              ) : null}
                              {comment.email ? (
                                <Button
                                  size="sm"
                                  variant="outline"
                                  disabled={
                                    banLoadingKey ===
                                    `email:${normalizeMatcherValue('email', comment.email)}`
                                  }
                                  onClick={() =>
                                    void createBlacklist(
                                      'email',
                                      comment.email ?? '',
                                      `评论#${comment.id} 快速封禁`,
                                    )
                                  }
                                >
                                  <Ban className="h-3.5 w-3.5" />
                                  封邮箱
                                </Button>
                              ) : null}
                              {comment.user_agent ? (
                                <Button
                                  size="sm"
                                  variant="outline"
                                  disabled={
                                    banLoadingKey ===
                                    `user_agent:${normalizeMatcherValue(
                                      'user_agent',
                                      comment.user_agent,
                                    )}`
                                  }
                                  onClick={() =>
                                    void createBlacklist(
                                      'user_agent',
                                      comment.user_agent ?? '',
                                      `评论#${comment.id} 快速封禁`,
                                    )
                                  }
                                >
                                  <Ban className="h-3.5 w-3.5" />
                                  封 UA
                                </Button>
                              ) : null}
                            </div>
                          </div>
                        </TableCell>
                        <TableCell>
                          <div className="flex flex-wrap gap-2">
                            <Button
                              size="sm"
                              variant="outline"
                              disabled={actingId === comment.id || Boolean(comment.approved)}
                              onClick={async () => {
                                try {
                                  setActingId(comment.id)
                                  await adminApi.updateComment(comment.id, { approved: true })
                                  toast.success('评论已通过。')
                                  await loadComments(false)
                                } catch (error) {
                                  toast.error(
                                    error instanceof ApiError ? error.message : '无法通过该评论。',
                                  )
                                } finally {
                                  setActingId(null)
                                }
                              }}
                            >
                              <CheckCheck className="h-4 w-4" />
                              通过
                            </Button>
                            <Button
                              size="sm"
                              variant="ghost"
                              disabled={actingId === comment.id || !comment.approved}
                              onClick={async () => {
                                try {
                                  setActingId(comment.id)
                                  await adminApi.updateComment(comment.id, { approved: false })
                                  toast.success('评论已移回待审核。')
                                  await loadComments(false)
                                } catch (error) {
                                  toast.error(
                                    error instanceof ApiError ? error.message : '无法更新评论状态。',
                                  )
                                } finally {
                                  setActingId(null)
                                }
                              }}
                            >
                              <XCircle className="h-4 w-4" />
                              隐藏
                            </Button>
                            <Button
                              size="sm"
                              variant="danger"
                              disabled={actingId === comment.id}
                              onClick={async () => {
                                if (!window.confirm('确定要永久删除这条评论吗？')) {
                                  return
                                }

                                try {
                                  setActingId(comment.id)
                                  await adminApi.deleteComment(comment.id)
                                  toast.success('评论已删除。')
                                  await loadComments(false)
                                } catch (error) {
                                  toast.error(
                                    error instanceof ApiError ? error.message : '无法删除评论。',
                                  )
                                } finally {
                                  setActingId(null)
                                }
                              }}
                            >
                              <Trash2 className="h-4 w-4" />
                              删除
                            </Button>
                          </div>
                        </TableCell>
                      </TableRow>
                    ))}
                    {!filteredComments.length ? (
                      <TableRow>
                        <TableCell colSpan={4} className="py-12 text-center">
                          <div className="flex flex-col items-center gap-3 text-muted-foreground">
                            <MessageSquareText className="h-8 w-8" />
                            <p>当前筛选条件下没有匹配的评论。</p>
                          </div>
                        </TableCell>
                      </TableRow>
                    ) : null}
                  </TableBody>
                </Table>
              </div>

              <div className="space-y-4">
                <Card>
                  <CardHeader>
                    <CardTitle>人物画像视图</CardTitle>
                    <CardDescription>按 IP / 邮箱 / UA 聚合，并查看同源评论轨迹。</CardDescription>
                  </CardHeader>
                  <CardContent className="space-y-3">
                    <Select
                      value={profileType}
                      onChange={(event) => setProfileType(event.target.value as MatcherType)}
                    >
                      <option value="ip">按 IP 聚合</option>
                      <option value="email">按邮箱聚合</option>
                      <option value="user_agent">按浏览器 UA 聚合</option>
                    </Select>

                    <div className="max-h-[230px] space-y-2 overflow-auto pr-1">
                      {personas.map((persona) => {
                        const key = `${profileType}:${normalizeMatcherValue(
                          profileType,
                          persona.matcherValue,
                        )}`
                        const blacklisted = blacklistedMatcherSet.has(key)

                        return (
                          <button
                            key={persona.matcherValue}
                            type="button"
                            className={`w-full rounded-2xl border px-3 py-2 text-left transition ${
                              selectedProfileValue === persona.matcherValue
                                ? 'border-primary/50 bg-primary/10'
                                : 'border-border/70 bg-background/40 hover:border-primary/30'
                            }`}
                            onClick={() => setSelectedProfileValue(persona.matcherValue)}
                          >
                            <div className="flex items-start justify-between gap-2">
                              <p
                                className="line-clamp-2 break-all text-xs font-mono text-foreground"
                                title={persona.matcherValue}
                              >
                                {profileType === 'user_agent'
                                  ? formatBrowserName(persona.matcherValue)
                                  : persona.matcherValue}
                              </p>
                              {blacklisted ? <Badge variant="danger">黑名单</Badge> : null}
                            </div>
                            <p className="mt-1 text-xs text-muted-foreground">
                              {persona.total} 条 · 待审核 {persona.pending} · 已通过{' '}
                              {persona.approved}
                            </p>
                          </button>
                        )
                      })}
                      {!personas.length ? (
                        <p className="rounded-2xl border border-border/70 px-3 py-6 text-center text-sm text-muted-foreground">
                          当前筛选条件下暂无可聚合来源。
                        </p>
                      ) : null}
                    </div>

                    {selectedPersona ? (
                      <div className="rounded-2xl border border-border/70 bg-background/40 p-3 text-xs text-muted-foreground">
                        <p>首次出现：{formatDateTime(selectedPersona.firstAt)}</p>
                        <p>最近评论：{formatDateTime(selectedPersona.latestAt)}</p>
                        <p className="font-mono">最近文章：{selectedPersona.latestPostSlug}</p>
                      </div>
                    ) : null}

                    <div className="flex gap-2">
                      <Input
                        value={manualReason}
                        onChange={(event) => setManualReason(event.target.value)}
                        placeholder="封禁原因（可选）"
                      />
                      <Button
                        variant="danger"
                        disabled={!selectedProfileValue}
                        onClick={() =>
                          void createBlacklist(profileType, selectedProfileValue, manualReason)
                        }
                      >
                        <Shield className="h-4 w-4" />
                        快速封禁
                      </Button>
                    </div>

                    <div className="space-y-2 rounded-2xl border border-border/70 bg-background/40 p-3">
                      <div className="flex flex-wrap items-center justify-between gap-2">
                        <p className="text-xs uppercase tracking-[0.18em] text-muted-foreground">
                          AI 风险分析
                        </p>
                        <Button
                          size="sm"
                          variant="outline"
                          disabled={!selectedProfileValue || analyzingPersona}
                          onClick={() => void analyzeSelectedPersona()}
                        >
                          <Bot className="h-4 w-4" />
                          {analyzingPersona ? '分析中...' : 'AI 分析'}
                        </Button>
                      </div>
                      {personaAnalysis ? (
                        <div className="space-y-2">
                          <p className="text-xs text-muted-foreground">
                            匹配评论 {personaAnalysis.total_comments} · 待审核{' '}
                            {personaAnalysis.pending_comments} · 涉及文章{' '}
                            {personaAnalysis.distinct_posts}
                          </p>
                          <pre className="max-h-[220px] whitespace-pre-wrap break-words rounded-xl border border-border/70 bg-background px-3 py-2 text-xs leading-6 text-foreground">
                            {personaAnalysis.analysis}
                          </pre>
                        </div>
                      ) : (
                        <p className="text-xs text-muted-foreground">
                          选中来源后可让 AI 输出风险等级、行为特征和处置建议。
                        </p>
                      )}

                      <div className="space-y-2 rounded-xl border border-border/70 bg-background/60 p-2.5">
                        <p className="text-xs uppercase tracking-[0.16em] text-muted-foreground">
                          最近分析记录
                        </p>
                        {personaLogsLoading ? (
                          <Skeleton className="h-16 rounded-xl" />
                        ) : personaLogs.length ? (
                          <div className="max-h-[220px] space-y-2 overflow-auto pr-1">
                            {personaLogs.map((item) => (
                              <button
                                key={item.id}
                                type="button"
                                className="w-full rounded-xl border border-border/70 bg-background px-3 py-2 text-left transition hover:border-primary/40"
                                onClick={() =>
                                  startTransition(() => {
                                    setPersonaAnalysis(toPersonaAnalysisFromLog(item))
                                  })
                                }
                              >
                                <p className="text-xs text-muted-foreground">
                                  #{item.id} · {formatDateTime(item.created_at)} · 评论 {item.total_comments}{' '}
                                  · 待审 {item.pending_comments}
                                </p>
                                <p className="line-clamp-2 mt-1 text-xs text-foreground">
                                  {item.analysis}
                                </p>
                              </button>
                            ))}
                          </div>
                        ) : (
                          <p className="text-xs text-muted-foreground">暂无历史分析记录。</p>
                        )}
                      </div>
                    </div>
                  </CardContent>
                </Card>

                <Card>
                  <CardHeader>
                    <CardTitle>同源评论轨迹</CardTitle>
                    <CardDescription>
                      当前按 {matcherLabel(profileType)} 查看，共 {selectedPersonaComments.length}{' '}
                      条。
                    </CardDescription>
                  </CardHeader>
                  <CardContent className="max-h-[260px] space-y-2 overflow-auto pr-1">
                    {selectedPersonaComments.map((comment) => (
                      <div key={comment.id} className="rounded-2xl border border-border/70 p-3">
                        <div className="flex flex-wrap items-center gap-2">
                          <Badge variant={comment.approved ? 'success' : 'warning'}>
                            {comment.approved ? '通过' : '待审'}
                          </Badge>
                          <span className="text-xs text-muted-foreground">
                            {formatDateTime(comment.created_at)}
                          </span>
                          <span className="text-xs font-mono text-muted-foreground">
                            {comment.post_slug ?? 'unknown-post'}
                          </span>
                        </div>
                        <p className="mt-2 text-sm text-muted-foreground">
                          {comment.content ?? '暂无内容'}
                        </p>
                      </div>
                    ))}
                    {!selectedPersonaComments.length ? (
                      <p className="rounded-2xl border border-border/70 px-3 py-6 text-center text-sm text-muted-foreground">
                        请选择来源查看轨迹。
                      </p>
                    ) : null}
                  </CardContent>
                </Card>

                <Card>
                  <CardHeader>
                    <CardTitle>黑名单管理</CardTitle>
                    <CardDescription>支持手动新增、停用与删除规则。</CardDescription>
                  </CardHeader>
                  <CardContent className="space-y-3">
                    <div className="grid gap-2 md:grid-cols-[150px_1fr]">
                      <Select
                        value={manualMatcherType}
                        onChange={(event) =>
                          setManualMatcherType(event.target.value as MatcherType)
                        }
                      >
                        <option value="ip">IP</option>
                        <option value="email">邮箱</option>
                        <option value="user_agent">浏览器 UA</option>
                      </Select>
                      <Input
                        value={manualMatcherValue}
                        onChange={(event) => setManualMatcherValue(event.target.value)}
                        placeholder="输入要封禁的值"
                      />
                    </div>
                    <div className="flex gap-2">
                      <Input
                        value={manualReason}
                        onChange={(event) => setManualReason(event.target.value)}
                        placeholder="原因（可选）"
                      />
                      <Button
                        onClick={async () => {
                          await createBlacklist(
                            manualMatcherType,
                            manualMatcherValue,
                            manualReason || '后台手动封禁',
                          )
                          setManualMatcherValue('')
                        }}
                        disabled={!manualMatcherValue.trim()}
                        data-testid="comment-blacklist-add"
                      >
                        <Shield className="h-4 w-4" />
                        新增
                      </Button>
                    </div>

                    <div className="max-h-[220px] space-y-2 overflow-auto pr-1">
                      {blacklist.map((item) => (
                        <div
                          key={item.id}
                          data-testid={`blacklist-item-${item.id}`}
                          className="rounded-2xl border border-border/70 bg-background/40 p-3"
                        >
                          <div className="flex flex-wrap items-center justify-between gap-2">
                            <p className="text-xs font-mono">
                              {item.matcher_type}:{' '}
                              {item.matcher_type === 'user_agent'
                                ? formatBrowserName(item.matcher_value)
                                : item.matcher_value}
                            </p>
                            <Badge variant={item.effective ? 'danger' : 'outline'}>
                              {item.effective ? '生效中' : '已停用/过期'}
                            </Badge>
                          </div>
                          {item.reason ? (
                            <p className="mt-1 text-xs text-muted-foreground">{item.reason}</p>
                          ) : null}
                          <div className="mt-2 flex flex-wrap gap-2">
                            <Button
                              size="sm"
                              variant="outline"
                              disabled={actingBlacklistId === item.id}
                              data-testid={`blacklist-toggle-${item.id}`}
                              onClick={async () => {
                                try {
                                  setActingBlacklistId(item.id)
                                  await adminApi.updateCommentBlacklist(item.id, {
                                    active: !item.active,
                                  })
                                  toast.success(item.active ? '规则已停用。' : '规则已启用。')
                                  await loadComments(false)
                                } catch (error) {
                                  toast.error(
                                    error instanceof ApiError
                                      ? error.message
                                      : '更新黑名单规则失败。',
                                  )
                                } finally {
                                  setActingBlacklistId(null)
                                }
                              }}
                            >
                              {item.active ? (
                                <ShieldOff className="h-4 w-4" />
                              ) : (
                                <Shield className="h-4 w-4" />
                              )}
                              {item.active ? '停用' : '启用'}
                            </Button>
                            <Button
                              size="sm"
                              variant="danger"
                              disabled={actingBlacklistId === item.id}
                              data-testid={`blacklist-delete-${item.id}`}
                              onClick={async () => {
                                if (!window.confirm('确定删除这条黑名单规则吗？')) {
                                  return
                                }
                                try {
                                  setActingBlacklistId(item.id)
                                  await adminApi.deleteCommentBlacklist(item.id)
                                  toast.success('规则已删除。')
                                  await loadComments(false)
                                } catch (error) {
                                  toast.error(
                                    error instanceof ApiError
                                      ? error.message
                                      : '删除黑名单规则失败。',
                                  )
                                } finally {
                                  setActingBlacklistId(null)
                                }
                              }}
                            >
                              <Trash2 className="h-4 w-4" />
                              删除
                            </Button>
                          </div>
                        </div>
                      ))}
                      {!blacklist.length ? (
                        <p className="rounded-2xl border border-border/70 px-3 py-6 text-center text-sm text-muted-foreground">
                          暂无黑名单规则。
                        </p>
                      ) : null}
                    </div>
                  </CardContent>
                </Card>
              </div>
            </div>
          )}
        </CardContent>
      </Card>
    </div>
  )
}
