<script setup lang="ts">
import { NButton, NDataTable, NModal, type DataTableColumns } from 'naive-ui'
import { computed, h, onMounted, reactive, ref } from 'vue'

import { getAuditEventDetail, listAuditEvents } from '@/api/tenant'
import DetailFieldList, { type DetailRow } from '@/components/DetailFieldList.vue'
import DetailHero, { type HeroChip } from '@/components/DetailHero.vue'
import FootNote from '@/components/FootNote.vue'
import JsonBlock from '@/components/JsonBlock.vue'
import StatusBadge from '@/components/StatusBadge.vue'
import { eventLabel } from '@/composables/useEventLabel'
import { fmtTime } from '@/composables/useFormat'
import type { AuditEvent } from '@/types/api'
import { useToast } from '@/composables/useToast'

const props = defineProps<{ cfgId: number }>()
const message = useToast()

const loading = ref(false)
const loadingMore = ref(false)
const error = ref('')
const rows = ref<AuditEvent[]>([])
// 服务端分窗回溯游标:空且 exhausted 表示已到 365 天保留期尽头
const cursor = ref('')
const exhausted = ref(false)

const pageCount = computed(() => Math.max(1, Math.ceil(rows.value.length / pagination.pageSize)))
const hasMore = computed(() => cursor.value !== '' && !exhausted.value)

/** 受控分页：字面量对象会在重渲染时被重建导致页大小切换失效 */
const pagination = reactive({
  page: 1,
  pageSize: 20,
  showSizePicker: true,
  pageSizes: [20, 50, 100],
  prefix: () =>
    `已加载 ${rows.value.length} 条 · ` +
    (loadingMore.value ? '正在加载更早…' : exhausted.value ? '已到 365 天保留期尽头' : '翻到末页自动加载更早'),
  onUpdatePage: (p: number) => {
    pagination.page = p
    // 懒加载触发点:翻到已加载数据的最后一页即向后端续取一批
    if (p >= pageCount.value) void fetchBatch()
  },
  onUpdatePageSize: (ps: number) => {
    pagination.pageSize = ps
    pagination.page = 1
  },
})

/** 重置信息流:回到最新一批 */
async function load() {
  loading.value = true
  error.value = ''
  rows.value = []
  cursor.value = ''
  exhausted.value = false
  pagination.page = 1
  try {
    await fetchBatch(true)
  } finally {
    loading.value = false
  }
}

/** 向更早方向续取一批(~100 条):追加去重后按时间重排;
 *  末页不满且还有更早数据时自动补一批,避免首屏残页 */
async function fetchBatch(first = false) {
  if (loadingMore.value || (!first && !hasMore.value)) return
  loadingMore.value = true
  try {
    const r = await listAuditEvents(props.cfgId, { cursor: cursor.value || undefined })
    const seen = new Set(rows.value.map((e) => e.eventId))
    rows.value = [...rows.value, ...r.items.filter((e) => !seen.has(e.eventId))].sort((a, b) =>
      (b.eventTime ?? '').localeCompare(a.eventTime ?? ''),
    )
    cursor.value = r.cursor ?? ''
    exhausted.value = r.exhausted
  } catch (e) {
    if (first) {
      error.value = e instanceof Error ? e.message : '查询失败'
    } else {
      // 追加失败不清空已有列表;游标过期时提示刷新重来
      message.error(e instanceof Error ? e.message : '继续加载失败,请刷新重新查询')
    }
  } finally {
    loadingMore.value = false
  }
  if (hasMore.value && rows.value.length < pagination.pageSize) void fetchBatch()
}

onMounted(() => void load())

// ---- 行点击详情:原始 JSON 懒取(命中服务端缓存秒开,过期走小窗重查) ----
const detail = ref<AuditEvent | null>(null)
const showDetail = ref(false)
const detailRaw = ref<unknown>(null)
const rawLoading = ref(false)
const rawGone = ref(false)

function rowProps(row: AuditEvent) {
  return {
    style: 'cursor: pointer',
    onClick: () => {
      detail.value = row
      showDetail.value = true
      void loadRaw(row)
    },
  }
}

async function loadRaw(row: AuditEvent) {
  detailRaw.value = null
  rawGone.value = false
  if (!row.eventId || !row.eventTime) {
    rawGone.value = true
    return
  }
  rawLoading.value = true
  try {
    const { raw } = await getAuditEventDetail(props.cfgId, {
      eventId: row.eventId,
      eventTime: row.eventTime,
    })
    detailRaw.value = raw
  } catch {
    rawGone.value = true
  } finally {
    rawLoading.value = false
  }
}

/** 摘要头 chips:操作者 / IP / 时间;完整长值仍在字段网格与原始 JSON 中 */
const heroChips = computed<HeroChip[]>(() => {
  const d = detail.value
  if (!d) return []
  return [
    { label: '操作者', value: d.principalName || '—' },
    { label: 'IP', value: d.ipAddress || '—', mono: true },
    { label: '时间', value: fmtTime(d.eventTime) },
  ]
})

/** 字段网格:头部未覆盖的补充字段;空值以「—」占位 */
const detailRows = computed<DetailRow[]>(() => {
  const d = detail.value
  if (!d) return []
  return [
    { label: '资源', value: d.resourceName, mono: true },
    { label: '区间', value: d.compartmentName },
    { label: '请求', value: `${d.requestAction} ${d.requestPath}`.trim(), mono: true },
    { label: '来源', value: d.source },
  ]
})

/** 主文本 + 次行小字的两行单元格；空值以「—」占位 */
function twoLine(main: string, sub: string) {
  return h('div', { class: 'min-w-0' }, [
    h('div', { class: 'truncate text-[13px]' }, main || '—'),
    h('div', { class: 'mt-px truncate text-xs text-ink-3' }, sub || '—'),
  ])
}

const columns: DataTableColumns<AuditEvent> = [
  {
    title: '时间',
    key: 'eventTime',
    width: 145,
    render: (row) =>
      h('span', { class: 'text-[13px] text-ink-2 tabular-nums whitespace-nowrap' }, fmtTime(row.eventTime)),
  },
  {
    title: '操作',
    key: 'eventName',
    minWidth: 200,
    render: (row) => twoLine(row.eventName, row.source),
  },
  {
    // 固定列宽让 truncate 生效:资源路径可长达数百字符,完整值点行查看
    title: '资源',
    key: 'resourceName',
    width: 200,
    render: (row) => twoLine(row.resourceName, row.compartmentName),
  },
  {
    title: '操作者',
    key: 'principalName',
    width: 180,
    render: (row) => twoLine(row.principalName, row.ipAddress),
  },
  {
    title: '结果',
    key: 'status',
    width: 90,
    render: (row) => {
      if (!row.status) return h('span', { class: 'text-[13px] text-ink-3' }, '—')
      const failed = Number(row.status) >= 400
      return h(StatusBadge, { kind: failed ? 'term' : 'run', label: row.status, dot: false })
    },
  },
]
</script>

<template>
  <div class="panel">
    <div class="flex flex-wrap items-center gap-2.5 border-b border-line-soft px-4.5 py-3">
      <div class="text-sm font-semibold">审计日志</div>
      <StatusBadge v-if="exhausted" kind="run" label="已加载全部保留期数据" :dot="false" />
      <div class="flex-1" />
      <NButton size="small" :loading="loading" @click="load()">刷新</NButton>
    </div>

    <div
      v-if="error"
      class="px-5 py-10 text-center text-[13px] text-ink-3"
    >
      {{ error }}
    </div>
    <NDataTable
      v-else
      :columns="columns"
      :data="rows"
      :loading="loading"
      :pagination="pagination"
      :scroll-x="820"
      :row-props="rowProps"
    />
    <FootNote>
      OCI Audit 免费且默认开启，事件保留 365 天，无需在云端做任何配置 ·
      实时查询不入库，事件通常延迟数分钟可见 ·
      已默认过滤遥测噪声（SummarizeMetricsData）与内网地址发起的服务互调 ·
      从最新事件起每批约 100 条，翻到末页自动向更早加载，空档期自动扩窗回溯 · 点击行查看完整详情
    </FootNote>

    <NModal v-model:show="showDetail" preset="card" closable :style="{ width: '720px', maxWidth: 'calc(100vw - 24px)' }">
      <template #header>
        <DetailHero
          v-if="detail"
          kind="审计事件 · OCI Audit"
          :title="eventLabel(detail.eventName) || detail.eventName"
          :sub="`${detail.eventName} · ${detail.source}`"
          :chips="heroChips"
        >
          <StatusBadge
            v-if="detail.status"
            :kind="Number(detail.status) >= 400 ? 'term' : 'run'"
            :label="`${Number(detail.status) >= 400 ? '失败' : '成功'} ${detail.status}`"
            :dot="false"
          />
        </DetailHero>
      </template>
      <div v-if="detail" class="flex flex-col gap-3.5">
        <DetailFieldList :rows="detailRows" :cols="2" />
        <JsonBlock
          v-if="detailRaw"
          :value="detailRaw"
          title="原始 JSON"
          meta="来自 Audit SDK"
          max-height="36vh"
          wide
        />
        <div v-else-if="rawLoading" class="rounded-[10px] border border-line-soft px-3.5 py-3 text-xs text-ink-3">
          正在取回原始事件…
        </div>
        <div v-else-if="rawGone" class="rounded-[10px] border border-line-soft px-3.5 py-3 text-xs text-ink-3">
          原始事件已不可取回（超出缓存与重查窗口），以上为列表精简字段
        </div>
      </div>
    </NModal>
  </div>
</template>
