﻿<template>
  <div class="rounded-3xl border border-border bg-card p-6">
    <div class="flex flex-wrap items-center justify-between gap-3">
      <p class="text-base font-semibold text-foreground">管理日志</p>
      <div class="text-xs text-muted-foreground">
        自动刷新：{{ autoRefreshEnabled ? '开启' : '关闭' }}
      </div>
    </div>

    <div class="mt-4 grid grid-cols-2 gap-3 md:grid-cols-3 xl:grid-cols-6">
      <div class="rounded-2xl border border-border bg-card px-4 py-3 text-center">
        <div class="text-[11px] text-muted-foreground">总数</div>
        <div class="mt-1 text-lg font-semibold text-foreground">{{ stats?.memory.total ?? 0 }}</div>
      </div>
      <div class="rounded-2xl border border-border bg-card px-4 py-3 text-center">
        <div class="text-[11px] text-muted-foreground">对话</div>
        <div class="mt-1 text-lg font-semibold text-foreground">{{ stats?.chat_count ?? 0 }}</div>
      </div>
      <div class="rounded-2xl border border-border bg-card px-4 py-3 text-center">
        <div class="text-[11px] text-muted-foreground">INFO</div>
        <div class="mt-1 text-lg font-semibold text-foreground">{{ stats?.memory.by_level.INFO ?? 0 }}</div>
      </div>
      <div class="rounded-2xl border border-border bg-card px-4 py-3 text-center">
        <div class="text-[11px] text-muted-foreground">WARNING</div>
        <div class="mt-1 text-lg font-semibold text-foreground">{{ stats?.memory.by_level.WARNING ?? 0 }}</div>
      </div>
      <div class="rounded-2xl border border-border bg-card px-4 py-3 text-center">
        <div class="text-[11px] text-muted-foreground">ERROR</div>
        <div class="mt-1 text-lg font-semibold" :class="stats?.memory.by_level.ERROR ? 'text-rose-600' : 'text-foreground'">
          {{ stats?.memory.by_level.ERROR ?? 0 }}
        </div>
      </div>
      <div class="rounded-2xl border border-border bg-card px-4 py-3 text-center">
        <div class="text-[11px] text-muted-foreground">缓存上限</div>
        <div class="mt-1 text-lg font-semibold text-foreground">{{ stats?.memory.capacity ?? 0 }}</div>
      </div>
    </div>

    <div class="mt-4 flex flex-wrap items-center gap-2 sm:flex-nowrap">
      <div class="w-44 shrink-0">
        <SelectMenu v-model="filters.level" :options="levelOptions" />
      </div>
      <input
        v-model.trim="filters.search"
        type="text"
        placeholder="搜索..."
        class="min-w-[200px] flex-1 rounded-2xl border border-border bg-background px-3 py-2 text-xs text-foreground sm:min-w-0"
      />
      <input
        v-model.number="filters.limit"
        type="number"
        min="10"
        max="1000"
        step="100"
        class="w-24 rounded-2xl border border-border bg-background px-3 py-2 text-xs text-foreground"
      />
      <button
        class="rounded-full border border-border px-4 py-2 text-xs font-medium text-foreground transition-colors
               hover:border-primary hover:text-primary"
        @click="fetchLogs"
      >
        查询
      </button>
      <button
        class="rounded-full border border-border px-4 py-2 text-xs font-medium text-foreground transition-colors
               hover:border-primary hover:text-primary"
        @click="exportLogs"
      >
        导出
      </button>
      <button
        class="rounded-full border border-border px-4 py-2 text-xs font-medium text-foreground transition-colors
               hover:border-primary hover:text-primary"
        @click="toggleView"
      >
        {{ rawView ? '结构化视图' : '原始视图' }}
      </button>
      <button
        class="rounded-full px-4 py-2 text-xs font-medium transition-colors"
        :class="autoRefreshEnabled ? 'bg-primary text-primary-foreground' : 'border border-border text-muted-foreground hover:text-foreground'"
        @click="toggleAutoRefresh"
      >
        自动刷新
      </button>
      <button
        class="rounded-full px-4 py-2 text-xs font-medium transition-colors"
        :class="hideTaskLogs ? 'bg-primary text-primary-foreground' : 'border border-border text-muted-foreground hover:text-foreground'"
        @click="hideTaskLogs = !hideTaskLogs"
      >
        隐藏刷新日志
      </button>
    </div>

    <div v-if="statusMessage" class="mt-3 text-xs" :class="statusToneClass">
      {{ statusMessage }}
    </div>

    <div v-if="errorMessage" class="mt-4 rounded-2xl bg-destructive/10 px-4 py-3 text-sm text-destructive">
      {{ errorMessage }}
    </div>

    <div
      v-if="rawView"
      ref="rawLogContainer"
      class="scrollbar-slim mt-4 max-h-[60vh] overflow-x-auto overflow-y-auto rounded-2xl border border-border bg-muted/30 px-4 py-3 text-[11px] text-muted-foreground"
    >
      <pre class="whitespace-pre font-mono leading-relaxed">{{ rawLogView.text }}</pre>
    </div>
    <div
      v-else
      ref="structuredLogContainer"
      class="scrollbar-slim mt-4 max-h-[60vh] space-y-3 overflow-y-auto rounded-2xl border border-border bg-card px-4 py-3"
    >
      <div v-if="structuredView.ungrouped.length === 0 && structuredView.groups.length === 0" class="text-xs text-muted-foreground">
        暂无日志
      </div>

      <div v-for="(log, index) in structuredView.ungrouped" :key="`u-${index}`">
        <div class="cv-auto flex flex-wrap items-center gap-2 rounded-xl border border-border bg-card px-3 py-2 text-xs">
          <div class="flex flex-wrap items-center gap-2">
            <span class="text-muted-foreground">{{ log.time }}</span>
            <span :class="levelBadgeClass(log.level)">{{ log.level }}</span>
            <span
              v-for="tag in log.tags"
              :key="tag"
              class="rounded px-2 py-0.5 text-[10px] font-semibold text-white"
              :style="{ backgroundColor: getCategoryColor(tag) }"
            >
              {{ tag }}
            </span>
            <span
              v-if="log.accountId"
              class="text-[11px] font-semibold"
              :style="{ color: getAccountColor(log.accountId) }"
            >
              {{ log.accountId }}
            </span>
          </div>
          <div class="w-full text-foreground md:w-auto md:flex-1">
            {{ log.text }}
          </div>
        </div>
      </div>

      <div v-for="group in structuredView.groups" :key="group.id" class="rounded-2xl border border-border bg-card">
        <button
          type="button"
          class="flex w-full flex-wrap items-center gap-2 rounded-2xl bg-secondary/40 px-4 py-3 text-left text-xs transition hover:bg-secondary/60"
          @click="toggleGroup(group.id)"
        >
          <span :class="statusBadgeClass(group.status)">{{ statusLabel(group.status) }}</span>
          <span class="text-muted-foreground">req_{{ group.id }}</span>
          <span v-if="group.accountId" class="text-xs font-semibold" :style="{ color: getAccountColor(group.accountId) }">
            {{ group.accountId }}
          </span>
          <span v-if="group.model" class="text-muted-foreground">{{ group.model }}</span>
          <span v-if="isGroupLimited(group)" class="text-[10px] text-muted-foreground">
            仅显示最近 {{ groupLogLimit }} 条
          </span>
          <span class="text-muted-foreground">{{ group.logs.length }} 条日志</span>
          <span
            class="ml-auto text-muted-foreground transition-transform"
            :class="{ 'rotate-90': !isCollapsed(group.id) }"
          >
            ▸
          </span>
        </button>
        <div v-if="!isCollapsed(group.id)" class="space-y-2 px-4 py-3">
          <div
            v-for="(log, logIndex) in visibleGroupLogs(group)"
            :key="`${group.id}-${logIndex}`"
            class="cv-auto flex flex-wrap items-center gap-2 rounded-xl border border-border bg-card px-3 py-2 text-xs"
          >
            <div class="flex flex-wrap items-center gap-2">
              <span class="text-muted-foreground">{{ log.time }}</span>
              <span :class="levelBadgeClass(log.level)">{{ log.level }}</span>
              <span
                v-for="tag in log.tags"
                :key="tag"
                class="rounded px-2 py-0.5 text-[10px] font-semibold text-white"
                :style="{ backgroundColor: getCategoryColor(tag) }"
              >
                {{ tag }}
              </span>
              <span
                v-if="log.accountId"
                class="text-[11px] font-semibold"
                :style="{ color: getAccountColor(log.accountId) }"
              >
                {{ log.accountId }}
              </span>
            </div>
            <div class="w-full text-foreground md:w-auto md:flex-1">
              {{ log.text }}
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

  <ConfirmDialog
    :open="confirmOpen"
    title="确认操作"
    message="确定要清空所有运行日志吗？"
    confirm-text="确认"
    cancel-text="取消"
    @confirm="clearLogs"
    @cancel="confirmOpen = false"
  />
</template>

<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import { logsApi } from '@/api'
import SelectMenu from '@/components/ui/SelectMenu.vue'
import ConfirmDialog from '@/components/ui/ConfirmDialog.vue'
import type { AdminLogStats, LogEntry } from '@/types/api'

type ParsedLogEntry = LogEntry & {
  tags: string[]
  accountId: string
  text: string
  reqId: string
}

type GroupedLog = {
  id: string
  logs: ParsedLogEntry[]
  status: string
  accountId: string
  model: string
}

type GroupedLogState = {
  ungrouped: ParsedLogEntry[]
  groups: GroupedLog[]
}

const logs = ref<LogEntry[]>([])
const parsedLogs = ref<ParsedLogEntry[]>([])
const groupedLogs = ref<GroupedLogState>({ ungrouped: [], groups: [] })
const stats = ref<AdminLogStats | null>(null)
const errorMessage = ref('')
const statusMessage = ref('')
const statusTone = ref<'success' | 'error'>('success')
const confirmOpen = ref(false)
const autoRefreshEnabled = ref(true)
const collapsedState = ref<Record<string, boolean>>({})
const rawView = ref(true)
const hideTaskLogs = ref(true)
const rawLogContainer = ref<HTMLDivElement | null>(null)
const structuredLogContainer = ref<HTMLDivElement | null>(null)
const structuredRenderLimit = 1000
const rawRenderLimit = 1000
const groupLogLimit = 200
const refreshIntervalMs = 3000
let timer: number | undefined
let isFetching = false

const filters = reactive({
  level: '',
  search: '',
  limit: 1000,
})

const levelOptions = [
  { label: '全部', value: '' },
  { label: 'INFO', value: 'INFO' },
  { label: 'WARNING', value: 'WARNING' },
  { label: 'ERROR', value: 'ERROR' },
]

const CATEGORY_COLORS: Record<string, string> = {
  SYSTEM: '#9e9e9e',
  CONFIG: '#607d8b',
  LOG: '#9e9e9e',
  AUTH: '#4caf50',
  SESSION: '#00bcd4',
  FILE: '#ff9800',
  CHAT: '#2196f3',
  API: '#8bc34a',
  CACHE: '#9c27b0',
  ACCOUNT: '#f44336',
  MULTI: '#673ab7',
}

const ACCOUNT_COLORS: Record<string, string> = {
  account_1: '#9c27b0',
  account_2: '#e91e63',
  account_3: '#00bcd4',
  account_4: '#4caf50',
  account_5: '#ff9800',
}

const statusToneClass = computed(() =>
  statusTone.value === 'error' ? 'text-destructive' : 'text-muted-foreground'
)

const getCategoryColor = (category: string) => CATEGORY_COLORS[category] || '#757575'
const getAccountColor = (accountId: string) => ACCOUNT_COLORS[accountId] || '#757575'

const levelBadgeClass = (level: LogEntry['level']) => {
  const base = 'rounded px-2 py-0.5 text-[10px] font-semibold'
  if (level === 'INFO') return `${base} bg-blue-100 text-blue-700`
  if (level === 'WARNING') return `${base} bg-amber-100 text-amber-700`
  if (level === 'ERROR' || level === 'CRITICAL') return `${base} bg-rose-100 text-rose-700`
  return `${base} bg-violet-100 text-violet-700`
}

const statusBadgeClass = (status: string) => {
  const base = 'rounded-md px-2 py-0.5 text-[11px] font-semibold'
  if (status === 'success') return `${base} bg-emerald-100 text-emerald-700`
  if (status === 'error') return `${base} bg-rose-100 text-rose-700`
  if (status === 'timeout') return `${base} bg-amber-100 text-amber-700`
  return `${base} bg-amber-100 text-amber-700`
}

const statusLabel = (status: string) => {
  if (status === 'success') return '成功'
  if (status === 'error') return '失败'
  if (status === 'timeout') return '超时'
  return '进行中'
}

const parseLogMessage = (message: string) => {
  let remaining = message
  const tags: string[] = []
  let accountId = ''
  const tagRegex = /^\[([A-Za-z0-9_]+)\]/

  while (true) {
    const match = remaining.match(tagRegex)
    if (!match) break
    const tag = match[1]
    remaining = remaining.slice(match[0].length).trim()

    if (tag.startsWith('req_')) {
      continue
    }
    if (tag.startsWith('account_')) {
      accountId = tag
      continue
    }
    tags.push(tag)
  }

  return { tags, accountId, text: remaining }
}

const parseLogEntry = (log: LogEntry): ParsedLogEntry => {
  const parsed = parseLogMessage(log.message)
  const reqMatch = log.message.match(/\[req_([a-z0-9]+)\]/i)
  return {
    ...log,
    ...parsed,
    reqId: reqMatch ? reqMatch[1] : '',
  }
}

const parseLogTime = (value: string) => {
  if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value)
  }
  if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
    return new Date(value.replace(' ', 'T'))
  }
  if (/^\d{2}:\d{2}:\d{2}$/.test(value)) {
    const now = new Date()
    const [hours, minutes, seconds] = value.split(':').map(Number)
    const parsed = new Date(now)
    parsed.setHours(hours, minutes, seconds, 0)
    return parsed
  }
  return null
}

const getGroupStatus = (groupLogs: LogEntry[]) => {
  const lastLog = groupLogs[groupLogs.length - 1]
  const lastMessage = lastLog.message

  if (lastMessage.includes('响应完成') || lastMessage.includes('非流式响应完成')) {
    return 'success'
  }
  if (lastLog.level === 'ERROR' || lastMessage.includes('失败')) {
    return 'error'
  }

  const parsedTime = parseLogTime(lastLog.time)
  if (parsedTime) {
    const diffMinutes = (Date.now() - parsedTime.getTime()) / 1000 / 60
    if (diffMinutes > 5) {
      return 'timeout'
    }
  }

  return 'in_progress'
}

const buildGroupedLogs = (items: ParsedLogEntry[]): GroupedLogState => {
  const groups = new Map<string, ParsedLogEntry[]>()
  const groupOrder: string[] = []
  const ungrouped: ParsedLogEntry[] = []

  items.forEach((log) => {
    if (log.reqId) {
      if (!groups.has(log.reqId)) {
        groups.set(log.reqId, [])
        groupOrder.push(log.reqId)
      }
      groups.get(log.reqId)?.push(log)
    } else {
      ungrouped.push(log)
    }
  })

  const groupList = groupOrder.map((id) => {
    const groupLogs = groups.get(id) || []
    const firstLog = groupLogs[0]
    const accountMatch = firstLog?.message.match(/\[(account_[^\]]+)\]/i)
    const modelMatch = firstLog?.message.match(/收到请求: ([^ |]+)/) || firstLog?.message.match(/Received request: ([^ |]+)/)

    return {
      id,
      logs: groupLogs,
      status: getGroupStatus(groupLogs),
      accountId: firstLog?.accountId || (accountMatch ? accountMatch[1] : ''),
      model: modelMatch ? modelMatch[1] : '',
    }
  })

  return { ungrouped, groups: groupList }
}

const TASK_LOG_PREFIXES = ['[REFRESH]', '[REGISTER]']

const isTaskLog = (message: string) =>
  TASK_LOG_PREFIXES.some(prefix => message.includes(prefix))

const structuredView = computed(() => {
  const ungrouped = groupedLogs.value.ungrouped
  const groups = groupedLogs.value.groups
  const limitedUngrouped = ungrouped.length > structuredRenderLimit
    ? ungrouped.slice(-structuredRenderLimit)
    : ungrouped
  const limitedGroups = groups.length > structuredRenderLimit
    ? groups.slice(-structuredRenderLimit)
    : groups

  return {
    ungrouped: limitedUngrouped,
    groups: limitedGroups,
    limited: ungrouped.length > limitedUngrouped.length || groups.length > limitedGroups.length,
    ungroupedTotal: ungrouped.length,
    groupsTotal: groups.length,
    ungroupedShowing: limitedUngrouped.length,
    groupsShowing: limitedGroups.length,
  }
})

const rawLogView = computed(() => {
  const total = parsedLogs.value.length
  const startIndex = total > rawRenderLimit ? total - rawRenderLimit : 0
  const slice = parsedLogs.value.slice(startIndex)
  const text = slice.map(log => `${log.time} | ${log.level} | ${log.message}`).join('\n')
  const showing = slice.length
  return {
    text,
    total,
    showing,
    limited: total > showing,
  }
})

const isCollapsed = (requestId: string) => collapsedState.value[requestId] === true

const toggleGroup = (requestId: string) => {
  collapsedState.value[requestId] = !isCollapsed(requestId)
  localStorage.setItem('log-fold-state', JSON.stringify(collapsedState.value))
}

const isGroupLimited = (group: GroupedLog) => group.logs.length > groupLogLimit

const visibleGroupLogs = (group: GroupedLog) => {
  if (group.logs.length <= groupLogLimit) return group.logs
  return group.logs.slice(-groupLogLimit)
}

const normalizeLimit = () => {
  if (!filters.limit || Number.isNaN(filters.limit)) {
    filters.limit = 300
  }
  filters.limit = Math.min(Math.max(filters.limit, 10), 1000)
}

const fetchLogs = async () => {
  if (isFetching) return
  isFetching = true
  errorMessage.value = ''
  statusMessage.value = ''
  normalizeLimit()
  try {
    const response = await logsApi.list({
      limit: filters.limit,
      level: filters.level || undefined,
      search: filters.search || undefined,
    })
    logs.value = response.logs
    const filtered = hideTaskLogs.value
      ? response.logs.filter(log => !isTaskLog(log.message))
      : response.logs
    parsedLogs.value = filtered.map(parseLogEntry)
    groupedLogs.value = buildGroupedLogs(parsedLogs.value)
    stats.value = response.stats
  } catch (error: any) {
    errorMessage.value = error.message || '日志加载失败'
  } finally {
    isFetching = false
    requestAnimationFrame(scrollToBottom)
  }
}

const exportLogs = async () => {
  statusMessage.value = ''
  statusTone.value = 'success'
  try {
    const response = await logsApi.list({
      limit: 1000,
      level: filters.level || undefined,
      search: filters.search || undefined,
    })
    const blob = new Blob(
      [JSON.stringify({ exported_at: new Date().toISOString(), logs: response.logs }, null, 2)],
      { type: 'application/json' }
    )
    const blobUrl = URL.createObjectURL(blob)
    const anchor = document.createElement('a')
    anchor.href = blobUrl
    anchor.download = `logs_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.json`
    anchor.click()
    URL.revokeObjectURL(blobUrl)
    statusMessage.value = '导出成功'
  } catch (error: any) {
    statusTone.value = 'error'
    statusMessage.value = error.message || '导出失败'
  }
}

const clearLogs = async () => {
  confirmOpen.value = false
  try {
    await logsApi.clear()
    statusTone.value = 'success'
    statusMessage.value = '已清空日志'
    await fetchLogs()
  } catch (error: any) {
    statusTone.value = 'error'
    statusMessage.value = error.message || '清空失败'
  }
}

const stopAutoRefresh = () => {
  if (timer) {
    window.clearTimeout(timer)
    timer = undefined
  }
}

const scheduleAutoRefresh = () => {
  if (!autoRefreshEnabled.value || document.hidden) return
  timer = window.setTimeout(async () => {
    await fetchLogs()
    scheduleAutoRefresh()
  }, refreshIntervalMs)
}

const startAutoRefresh = () => {
  stopAutoRefresh()
  scheduleAutoRefresh()
}

const toggleAutoRefresh = () => {
  autoRefreshEnabled.value = !autoRefreshEnabled.value
  if (autoRefreshEnabled.value) {
    startAutoRefresh()
  } else {
    stopAutoRefresh()
  }
}

const toggleView = () => {
  rawView.value = !rawView.value
  requestAnimationFrame(scrollToBottom)
}

const scrollToBottom = () => {
  if (rawView.value && rawLogContainer.value) {
    rawLogContainer.value.scrollTop = rawLogContainer.value.scrollHeight
  }
  if (!rawView.value && structuredLogContainer.value) {
    structuredLogContainer.value.scrollTop = structuredLogContainer.value.scrollHeight
  }
}

const handleVisibilityChange = () => {
  if (document.hidden) {
    stopAutoRefresh()
  } else if (autoRefreshEnabled.value) {
    startAutoRefresh()
  }
}

onMounted(() => {
  const saved = localStorage.getItem('log-fold-state')
  if (saved) {
    try {
      collapsedState.value = JSON.parse(saved)
    } catch {
      collapsedState.value = {}
    }
  }
  fetchLogs()
  startAutoRefresh()
  document.addEventListener('visibilitychange', handleVisibilityChange)
})

watch(hideTaskLogs, () => {
  // Re-filter from raw logs when toggle changes
  const filtered = hideTaskLogs.value
    ? logs.value.filter(log => !isTaskLog(log.message))
    : logs.value
  parsedLogs.value = filtered.map(parseLogEntry)
  groupedLogs.value = buildGroupedLogs(parsedLogs.value)
  requestAnimationFrame(scrollToBottom)
})

onBeforeUnmount(() => {
  stopAutoRefresh()
  document.removeEventListener('visibilitychange', handleVisibilityChange)
})
</script>
