<script setup lang="ts">
import { useAuthSession } from '../../../composables/useAuthSession'

definePageMeta({ title: '媒体存储校验' })

type RefDetail = { ownerType: string; ownerId: number }

type AuditRow = {
  id: number
  userId: number
  storageKey: string
  refCount: number
  refs: RefDetail[]
}

type AuditReport = {
  scannedAt: string
  dbRowCount: number
  diskFileCount: number
  missingOnDisk: AuditRow[]
  invalidStorageKey: AuditRow[]
  onDiskNotInDb: string[]
}

function refSourceLabel(ownerType: string, ownerId: number): string {
  if (ownerType === 'post') {
    return `文章 #${ownerId}`
  }
  if (ownerType === 'profile') {
    return `个人资料 #${ownerId}`
  }
  return `${ownerType} #${ownerId}`
}

function formatRefSources(refs: RefDetail[]): string {
  return refs.map((r) => refSourceLabel(r.ownerType, r.ownerId)).join(' · ')
}

const { user, refresh } = useAuthSession()
const { fetchData } = useClientApi()
const toast = useToast()

const loading = ref(false)
const cleaning = ref(false)
const report = ref<AuditReport | null>(null)
const cleanupOpen = ref(false)

async function ensureAdmin() {
  await refresh(true)
  if (user.value?.role !== 'admin') {
    await navigateTo('/me')
  }
}

async function runAudit() {
  loading.value = true
  try {
    report.value = await fetchData<AuditReport>('/api/admin/media/storage-audit')
    toast.add({ title: '校验完成', color: 'success' })
  } finally {
    loading.value = false
  }
}

async function runCleanup() {
  cleaning.value = true
  try {
    const { removed } = await fetchData<{ removed: number; removedIds: number[] }>(
      '/api/admin/media/storage-audit-cleanup',
      { method: 'POST', body: {} },
    )
    toast.add({ title: `已移除 ${removed} 条失效记录（无 media_refs 且磁盘无文件）`, color: 'success' })
    cleanupOpen.value = false
    await runAudit()
  } finally {
    cleaning.value = false
  }
}

const safeToCleanupCount = computed(() => {
  if (!report.value) {
    return 0
  }
  return report.value.missingOnDisk.filter((m) => m.refCount === 0).length
})

const riskyMissingCount = computed(() => {
  if (!report.value) {
    return 0
  }
  return report.value.missingOnDisk.filter((m) => m.refCount > 0).length
})

const cleanupDescription = computed(() => {
  const n = safeToCleanupCount.value
  return `将删除 ${n} 条 media_assets 记录：磁盘上无对应文件，且当前无任何 media_refs 引用（文章/个人资料等）。此操作不可撤销。`
})

onMounted(async () => {
  await ensureAdmin()
})
</script>

<template>
  <UContainer class="py-8 space-y-6 w-full max-w-[min(100%,88rem)] px-4 sm:px-6">
    <div class="flex flex-wrap justify-between items-start gap-3">
      <div>
        <h1 class="text-2xl font-semibold">
          媒体存储校验
        </h1>
        <p class="text-sm text-muted mt-1 max-w-3xl">
          比对 <code class="text-xs">public/assets</code> 与表 <code class="text-xs">media_assets</code>：
          库中有记录但文件缺失、非法 storageKey、以及磁盘上未登记的文件。
          「一键清理」仅删除<strong>无 media_refs 引用</strong>且<strong>磁盘上确实没有文件</strong>的库记录，不会删磁盘文件。
        </p>
      </div>
      <UButton to="/me" variant="ghost" size="sm">
        返回控制台
      </UButton>
    </div>

    <div class="flex flex-wrap items-center gap-3">
      <UButton :loading="loading" @click="runAudit">
        运行校验
      </UButton>
      <UButton
        v-if="report && safeToCleanupCount > 0"
        color="warning"
        variant="outline"
        :disabled="loading"
        @click="cleanupOpen = true"
      >
        清理失效库记录（{{ safeToCleanupCount }}）
      </UButton>
    </div>

    <div v-if="report" class="space-y-6">
      <div class="text-sm text-muted flex flex-wrap gap-x-4 gap-y-1">
        <span>扫描时间：{{ new Date(report.scannedAt).toLocaleString('zh-CN') }}</span>
        <span>库行数：{{ report.dbRowCount }}</span>
        <span>磁盘文件数：{{ report.diskFileCount }}</span>
      </div>

      <UCard v-if="riskyMissingCount > 0">
        <template #header>
          <span class="text-warning">需人工处理：文件缺失但仍被引用（{{ riskyMissingCount }}）</span>
        </template>
        <p class="text-sm text-muted mb-3">
          「引用来源」列出 media_refs 的 owner_type / owner_id（如文章 id、用户资料 user id）。请先修改对应正文、封面或个人资料中的图片，再处理库记录；勿使用「清理失效库记录」删除这些行。
        </p>
        <div class="overflow-x-auto rounded border border-default">
          <table class="w-full text-sm">
            <thead>
              <tr class="text-left text-muted border-b border-default bg-elevated/40">
                <th class="p-2">
                  id
                </th>
                <th class="p-2">
                  userId
                </th>
                <th class="p-2">
                  storageKey
                </th>
                <th class="p-2">
                  引用数
                </th>
                <th class="p-2 min-w-[12rem]">
                  引用来源（owner_type）
                </th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="row in report.missingOnDisk.filter((m) => m.refCount > 0)" :key="row.id" class="border-b border-default/60">
                <td class="p-2 tabular-nums">
                  {{ row.id }}
                </td>
                <td class="p-2 tabular-nums">
                  {{ row.userId }}
                </td>
                <td class="p-2 font-mono text-xs">
                  {{ row.storageKey }}
                </td>
                <td class="p-2 tabular-nums">
                  {{ row.refCount }}
                </td>
                <td class="p-2 text-xs align-top">
                  {{ formatRefSources(row.refs) }}
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </UCard>

      <UCard>
        <template #header>
          库有记录但磁盘无文件（且无引用时可清理）：{{ report.missingOnDisk.length }}
        </template>
        <UEmpty v-if="!report.missingOnDisk.length" title="无" />
        <div v-else class="overflow-x-auto rounded border border-default">
          <table class="w-full text-sm">
            <thead>
              <tr class="text-left text-muted border-b border-default bg-elevated/40">
                <th class="p-2">
                  id
                </th>
                <th class="p-2">
                  userId
                </th>
                <th class="p-2">
                  storageKey
                </th>
                <th class="p-2">
                  引用数
                </th>
                <th class="p-2 min-w-[12rem]">
                  引用来源（owner_type）
                </th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="row in report.missingOnDisk" :key="row.id" class="border-b border-default/60">
                <td class="p-2 tabular-nums">
                  {{ row.id }}
                </td>
                <td class="p-2 tabular-nums">
                  {{ row.userId }}
                </td>
                <td class="p-2 font-mono text-xs">
                  {{ row.storageKey }}
                </td>
                <td class="p-2 tabular-nums">
                  {{ row.refCount }}
                </td>
                <td class="p-2 text-xs align-top">
                  <span v-if="row.refCount === 0" class="text-muted">—</span>
                  <span v-else>{{ formatRefSources(row.refs) }}</span>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </UCard>

      <UCard v-if="report.invalidStorageKey.length">
        <template #header>
          非法或可疑的 storageKey：{{ report.invalidStorageKey.length }}
        </template>
        <div class="overflow-x-auto rounded border border-default">
          <table class="w-full text-sm">
            <thead>
              <tr class="text-left text-muted border-b border-default bg-elevated/40">
                <th class="p-2">
                  id
                </th>
                <th class="p-2">
                  userId
                </th>
                <th class="p-2">
                  storageKey
                </th>
                <th class="p-2">
                  引用数
                </th>
                <th class="p-2 min-w-[12rem]">
                  引用来源（owner_type）
                </th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="row in report.invalidStorageKey" :key="row.id" class="border-b border-default/60">
                <td class="p-2 tabular-nums">
                  {{ row.id }}
                </td>
                <td class="p-2 tabular-nums">
                  {{ row.userId }}
                </td>
                <td class="p-2 font-mono text-xs break-all">
                  {{ row.storageKey }}
                </td>
                <td class="p-2 tabular-nums">
                  {{ row.refCount }}
                </td>
                <td class="p-2 text-xs align-top">
                  <span v-if="row.refCount === 0" class="text-muted">—</span>
                  <span v-else>{{ formatRefSources(row.refs) }}</span>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </UCard>

      <UCard>
        <template #header>
          磁盘有文件但未在 media_assets 登记：{{ report.onDiskNotInDb.length }}
        </template>
        <p class="text-sm text-muted mb-3">
          可能是历史遗留或手动拷贝；需自行决定是否删除磁盘文件（本页不自动删文件）。
        </p>
        <UEmpty v-if="!report.onDiskNotInDb.length" title="无" />
        <ul v-else class="font-mono text-xs max-h-64 overflow-y-auto space-y-1 border border-default rounded p-3 bg-elevated/30">
          <li v-for="name in report.onDiskNotInDb" :key="name">
            {{ name }}
          </li>
        </ul>
      </UCard>
    </div>

    <UModal
      v-model:open="cleanupOpen"
      title="确认清理失效库记录"
      :description="cleanupDescription"
    >
      <template #footer>
        <div class="flex justify-end gap-2">
          <UButton variant="outline" color="neutral" :disabled="cleaning" @click="cleanupOpen = false">
            取消
          </UButton>
          <UButton color="warning" :loading="cleaning" @click="runCleanup">
            确认清理
          </UButton>
        </div>
      </template>
    </UModal>
  </UContainer>
</template>
