<template>
  <v-container fluid class="pa-4">
    <template v-if="detailId">
      <div class="d-flex align-center flex-wrap ga-2 mb-4">
        <v-btn variant="text" prepend-icon="mdi-arrow-left" @click="router.push('/executions')">
          返回列表
        </v-btn>
        <h1 class="text-h6">{{ detailTitle }}</h1>
        <v-chip
          v-if="execDetail"
          :color="execStatusColor(execDetail.status)"
          size="small"
          variant="tonal"
          label
        >
          {{ execStatusLabel(execDetail.status) }}
        </v-chip>
        <v-spacer />
        <v-btn
          v-if="execDetail?.long_task && detailKind === 'script'"
          size="small"
          variant="tonal"
          :loading="detailLoading"
          @click="loadExecDetail(true)"
        >
          拉取日志
        </v-btn>
        <v-btn
          v-if="detailKind === 'script' && execDetail && (execDetail.status === 'running' || execDetail.status === 'partial')"
          size="small"
          variant="text"
          color="error"
          @click="stopCurrent"
        >
          停止
        </v-btn>
        <v-btn
          v-if="detailKind === 'script' && execDetail && (execDetail.status === 'failed' || execDetail.status === 'partial')"
          size="small"
          variant="tonal"
          color="primary"
          @click="retryCurrent"
        >
          重试失败项
        </v-btn>
      </div>

      <v-card v-if="detailLoading && !execDetail" elevation="0" border rounded="lg" class="pa-8 text-center">
        <v-progress-circular indeterminate color="primary" />
      </v-card>

      <v-card v-else-if="execDetail" elevation="0" border rounded="lg">
        <v-card-text>
          <div class="text-caption text-medium-emphasis mb-2">
            {{ formatProgress(execDetail.progress) }} · 操作人 {{ execDetail.operator || '—' }}
          </div>
          <pre
            v-if="detailKind === 'script'"
            class="text-body-2 mb-3 pa-3 rounded bg-surface-variant overflow-auto"
            style="max-height: 100px"
          >{{ execDetail.command }}</pre>
          <v-chip v-else size="small" variant="tonal" color="info" class="mb-3" label>
            {{ executionRecordKindLabel(detailKind) }} · {{ execDetail.label || execDetail.op || '—' }}
          </v-chip>

          <div v-if="execDetailStats.total" class="d-flex flex-wrap ga-2 mb-3">
            <v-chip size="small" variant="tonal" label>共 {{ execDetailStats.total }} 台</v-chip>
            <v-chip v-if="execDetailStats.success" color="success" size="small" variant="tonal" label>
              成功 {{ execDetailStats.success }}
            </v-chip>
            <v-chip v-if="execDetailStats.failed" color="error" size="small" variant="tonal" label>
              失败 {{ execDetailStats.failed }}
            </v-chip>
            <v-chip v-if="execDetailStats.pending" color="info" size="small" variant="tonal" label>
              进行中 {{ execDetailStats.pending }}
            </v-chip>
          </div>

          <v-alert
            v-if="execDetailFailedNames.length"
            type="error"
            variant="tonal"
            density="compact"
            class="mb-3"
            :title="`失败 ${execDetailFailedNames.length} 台`"
          >
            <div class="text-body-2">{{ execDetailFailedNames.join('、') }}</div>
          </v-alert>

          <div class="d-flex align-center flex-wrap ga-2 mb-3">
            <span class="text-caption text-medium-emphasis">筛选</span>
            <v-btn-toggle v-model="detailRowFilter" mandatory density="compact" color="primary" variant="outlined" divided>
              <v-btn value="all" size="small">全部</v-btn>
              <v-btn value="failed" size="small">仅失败</v-btn>
              <v-btn value="success" size="small">仅成功</v-btn>
            </v-btn-toggle>
            <template v-if="execDetailStats.failed > 0">
              <v-divider vertical class="mx-1" />
              <span class="text-caption text-medium-emphasis">视图</span>
              <v-btn-toggle v-model="detailViewMode" mandatory density="compact" color="primary" variant="outlined" divided>
                <v-btn value="list" size="small">列表</v-btn>
                <v-btn value="group" size="small">按错误分组</v-btn>
              </v-btn-toggle>
              <v-btn size="small" variant="tonal" prepend-icon="mdi-content-copy" @click="copyFailedList">
                复制失败清单
              </v-btn>
              <v-btn
                v-if="detailViewMode === 'group'"
                size="small"
                variant="tonal"
                prepend-icon="mdi-content-copy"
                @click="copyFailureGroupSummary"
              >
                复制分组摘要
              </v-btn>
            </template>
          </div>

          <v-table v-if="detailViewMode === 'list'" density="compact">
            <thead>
              <tr>
                <th>服务器</th>
                <th>状态</th>
                <th style="width: 72px">退出码</th>
                <th>错误摘要</th>
                <th style="width: 120px" class="text-end">操作</th>
              </tr>
            </thead>
            <tbody>
              <tr
                v-for="row in filteredExecDetailRows"
                :key="row.serverId"
                :class="{ 'exec-detail-row--failed': row.failed }"
                class="cursor-pointer"
                @click="toggleDetailRow(row.serverId)"
              >
                <td>
                  <div class="font-weight-medium">{{ row.serverName }}</div>
                  <div v-if="row.serverDomain" class="text-caption text-medium-emphasis">{{ row.serverDomain }}</div>
                </td>
                <td>
                  <v-chip
                    :color="execServerStatusColor(row.status, row.phase, row.exitCode)"
                    size="x-small"
                    variant="tonal"
                    label
                  >
                    {{ execServerStatusLabel(row.status, row.phase) }}
                  </v-chip>
                </td>
                <td :class="{ 'text-error font-weight-medium': row.failed }">{{ row.exitCode ?? '—' }}</td>
                <td class="text-truncate" style="max-width: 280px" :title="row.summary">{{ row.summary }}</td>
                <td class="text-end" @click.stop>
                  <v-btn
                    size="x-small"
                    variant="text"
                    icon="mdi-console"
                    title="打开终端"
                    @click="openServerTerminal(row.serverIdNum)"
                  />
                </td>
              </tr>
              <tr v-if="filteredExecDetailRows.length === 0">
                <td colspan="5" class="text-center text-medium-emphasis py-4">当前筛选无记录</td>
              </tr>
            </tbody>
          </v-table>

          <v-expansion-panels
            v-else-if="execDetailFailureGroups.length"
            v-model="expandedFailureGroups"
            multiple
            variant="accordion"
          >
            <v-expansion-panel v-for="(group, idx) in execDetailFailureGroups" :key="`${idx}-${group.errorKey}`">
              <v-expansion-panel-title>
                <div class="d-flex align-center ga-2 flex-grow-1 min-width-0">
                  <span class="text-body-2 text-truncate" :title="group.errorKey">{{ group.errorKey }}</span>
                  <v-chip color="error" size="x-small" variant="tonal" label>{{ group.count }} 台</v-chip>
                </div>
              </v-expansion-panel-title>
              <v-expansion-panel-text>
                <div v-for="row in group.rows" :key="row.serverId" class="text-body-2 py-1">
                  {{ row.serverName }}
                  <span v-if="row.serverDomain" class="text-medium-emphasis">（{{ row.serverDomain }}）</span>
                </div>
              </v-expansion-panel-text>
            </v-expansion-panel>
          </v-expansion-panels>
        </v-card-text>
      </v-card>
    </template>

    <template v-else>
      <div class="d-flex align-center flex-wrap ga-2 mb-4">
        <h1 class="text-h6">执行记录</h1>
        <v-spacer />
        <v-btn variant="tonal" prepend-icon="mdi-refresh" :loading="listLoading" @click="loadList">
          刷新
        </v-btn>
      </div>

      <v-card elevation="0" border rounded="lg">
        <v-card-text>
          <v-row dense class="mb-2">
            <v-col cols="12" sm="4">
              <v-select
                v-model="kindFilter"
                :items="kindFilterOptions"
                label="类型"
                variant="outlined"
                density="compact"
                hide-details
                clearable
              />
            </v-col>
            <v-col cols="12" sm="4">
              <v-select
                v-model="statusFilter"
                :items="statusFilterOptions"
                label="状态筛选"
                variant="outlined"
                density="compact"
                hide-details
                clearable
              />
            </v-col>
          </v-row>
          <v-data-table-server
            :headers="listHeaders"
            :items="executions"
            :items-length="total"
            :loading="listLoading"
            :items-per-page="perPage"
            :page="page"
            @update:page="onPageChange"
            @update:items-per-page="onPerPageChange"
          >
            <template #item.kind="{ item }">
              <v-chip size="x-small" variant="tonal" label>
                {{ executionRecordKindLabel(item.kind) }}
              </v-chip>
            </template>
            <template #item.label="{ item }">
              {{ item.label }}
            </template>
            <template #item.status="{ item }">
              <v-chip :color="execStatusColor(item.status)" size="x-small" variant="tonal" label>
                {{ execStatusLabel(item.status) }}
              </v-chip>
            </template>
            <template #item.progress="{ item }">
              {{ formatProgress(item.progress) }}
            </template>
            <template #item.server_count="{ item }">
              {{ item.server_count }}
            </template>
            <template #item.started_at="{ item }">
              <span class="text-caption text-medium-emphasis">{{ formatDateTimeBeijing(item.started_at) }}</span>
            </template>
            <template #item.actions="{ item }">
              <v-btn size="x-small" variant="text" color="primary" @click="openDetail(item)">
                详情
              </v-btn>
            </template>
            <template #no-data>
              <div class="text-center text-medium-emphasis py-6">暂无执行记录</div>
            </template>
          </v-data-table-server>
        </v-card-text>
      </v-card>
    </template>
  </v-container>
</template>

<script setup lang="ts">
import { ref, computed, watch, onMounted, onActivated, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { http } from '@/api'
import { useSnackbar } from '@/composables/useSnackbar'
import {
  onScriptExecutionComplete,
  registerScriptExecution,
} from '@/composables/useScriptExecutionQueue'
import { useScriptExecutionListRefresh } from '@/composables/useScriptExecutionListRefresh'
import { showScriptSubmitToast } from '@/composables/useScriptSubmitToast'
import { useServerList } from '@/composables/useServerList'
import { formatApiError } from '@/utils/apiError'
import type { ScriptExecItem, ExecutionRecordItem } from '@/types/api'
import { DATA_TABLE_ITEMS_PER_PAGE_OPTIONS, headersWithoutSort } from '@/constants/dataTable'
import { fetchLimitOffset } from '@/utils/paginatedFetch'
import {
  executionRecordKindLabel,
  parseExecutionRecordKind,
  executionRecordPath,
} from '@/utils/executionRecordRoute'
import {
  execStatusLabel,
  execStatusColor,
  execServerStatusLabel,
  execServerStatusColor,
  isExecServerFailed,
  isExecServerPending,
  summarizeExecServerResults,
  resolveExecServerName,
} from '@/utils/scriptExecutionLabels'
import { groupFailedExecRows } from '@/utils/execDetailGrouping'
import { formatDateTimeBeijing } from '@/utils/datetime'

defineOptions({ name: 'ScriptRunsPage' })

const route = useRoute()
const router = useRouter()
const snackbar = useSnackbar()
const { servers: serverList, loadServers } = useServerList()

const detailId = computed(() => {
  const raw = route.params.id
  if (!raw) return null
  const n = Number(raw)
  return Number.isFinite(n) ? n : null
})

const detailKind = computed(() => parseExecutionRecordKind(route.query.kind))

const detailTitle = computed(() => {
  const prefix = detailKind.value === 'server_batch' ? '服务器任务' : '脚本执行'
  return `${prefix} #${detailId.value ?? ''}`
})

const listLoading = ref(false)
const executions = ref<ExecutionRecordItem[]>([])
const total = ref(0)
const page = ref(1)
const perPage = ref(20)
const statusFilter = ref<string | null>(null)
const kindFilter = ref<'script' | 'server_batch' | null>(null)

const statusFilterOptions = [
  { title: '执行中', value: 'running' },
  { title: '已完成', value: 'completed' },
  { title: '部分成功', value: 'partial' },
  { title: '失败', value: 'failed' },
  { title: '已取消', value: 'cancelled' },
]

const kindFilterOptions = [
  { title: '脚本', value: 'script' },
  { title: '服务器', value: 'server_batch' },
]

const listHeaders = headersWithoutSort([
  { title: 'ID', key: 'id', width: 72 },
  { title: '类型', key: 'kind', width: 88 },
  { title: '任务', key: 'label' },
  { title: '状态', key: 'status', width: 100 },
  { title: '进度', key: 'progress', width: 88 },
  { title: '台数', key: 'server_count', width: 72 },
  { title: '操作人', key: 'operator', width: 88 },
  { title: '开始时间', key: 'started_at', width: 168 },
  { title: '操作', key: 'actions', width: 88 },
])

const execDetail = ref<ScriptExecItem | null>(null)
const detailLoading = ref(false)
const detailRowFilter = ref<'all' | 'failed' | 'success'>('all')
const detailViewMode = ref<'list' | 'group'>('list')
const expandedRowId = ref<string | null>(null)
const expandedFailureGroups = ref<number[]>([])

let detailPollTimer: ReturnType<typeof setInterval> | null = null

interface ExecDetailRow {
  serverId: string
  serverIdNum: number
  serverName: string
  serverDomain: string
  status: string
  phase?: string
  exitCode: number | null
  summary: string
  fullOutput: string
  failed: boolean
  pending: boolean
}

const serverBriefById = computed(() => new Map(serverList.value.map(s => [s.id, s])))

function formatProgress(p: string | undefined) {
  return p || '—'
}

function parseServerCount(serverIds: string | undefined): number {
  if (!serverIds) return 0
  try {
    const arr = JSON.parse(serverIds) as unknown
    return Array.isArray(arr) ? arr.length : 0
  } catch {
    return 0
  }
}

function execLabel(item: ScriptExecItem): string {
  if (item.label) return item.label
  const cmd = (item.command || '').replace(/^\[long_task\] /, '')
  if (cmd.length > 48) return cmd.slice(0, 48) + '…'
  return cmd || `执行 #${item.id}`
}

function buildExecOutput(entry: ScriptExecItem['results'][string]): { summary: string; full: string } {
  const stderr = (entry.stderr || '').trim()
  const stdout = (entry.stdout || '').trim()
  const message = (entry.message || '').trim()
  const full = stderr || stdout || message || '—'
  const summary = (stderr || message || stdout || '').slice(0, 200) || '—'
  return { summary, full }
}

const execDetailRows = computed<ExecDetailRow[]>(() => {
  if (!execDetail.value?.results) return []
  const briefs = serverBriefById.value
  return Object.entries(execDetail.value.results)
    .filter(([k]) => k !== '_meta' && /^\d+$/.test(k))
    .map(([serverId, entry]) => {
      const idNum = Number(serverId)
      const brief = briefs.get(idNum)
      const status = String(entry.status || '')
      const phase = entry.phase ? String(entry.phase) : undefined
      const exitCode = entry.exit_code ?? null
      const output = buildExecOutput(entry)
      return {
        serverId,
        serverIdNum: idNum,
        serverName: resolveExecServerName(
          serverId,
          execDetail.value?.server_names,
          brief?.name,
        ),
        serverDomain: brief?.domain || '',
        status,
        phase,
        exitCode,
        summary: output.summary,
        fullOutput: output.full,
        failed: isExecServerFailed(status, phase, exitCode),
        pending: isExecServerPending(phase),
      }
    })
})

const filteredExecDetailRows = computed(() => {
  if (detailRowFilter.value === 'failed') return execDetailRows.value.filter(r => r.failed)
  if (detailRowFilter.value === 'success') return execDetailRows.value.filter(r => !r.failed && !r.pending)
  return execDetailRows.value
})

const execDetailStats = computed(() =>
  summarizeExecServerResults(execDetail.value?.results, execDetail.value?.server_ids),
)

const execDetailFailedNames = computed(() =>
  execDetailRows.value.filter(r => r.failed).map(r => r.serverName),
)

const execDetailFailureGroups = computed(() =>
  groupFailedExecRows(execDetailRows.value.filter(r => r.failed)),
)

async function loadList(silent = false) {
  if (!silent) listLoading.value = true
  try {
    const res = await fetchLimitOffset<ExecutionRecordItem>(
      async (limit, offset) => {
        const params: Record<string, unknown> = { limit, offset }
        if (statusFilter.value) params.status = statusFilter.value
        if (kindFilter.value) params.kind = kindFilter.value
        const data = await http.get<{ items: ExecutionRecordItem[]; total: number }>(
          '/execution-records',
          params,
        )
        return { items: data.items || [], total: data.total || 0 }
      },
      page.value,
      perPage.value,
    )
    executions.value = res.items
    total.value = res.total
  } catch (e: unknown) {
    if (!silent) {
      executions.value = []
      total.value = 0
      snackbar(formatApiError(e, '加载失败'), 'error')
    }
  } finally {
    if (!silent) listLoading.value = false
  }
}

function onPageChange(p: number) {
  page.value = p
  loadList()
}

function onPerPageChange(n: number) {
  perPage.value = n
  page.value = 1
  loadList()
}

function openDetail(item: ExecutionRecordItem) {
  router.push(executionRecordPath(item.id, item.kind))
}

async function loadExecDetail(fetchLogs = false, silent = false) {
  if (!detailId.value) return
  if (!silent) detailLoading.value = true
  try {
    const q = new URLSearchParams({ kind: detailKind.value })
    if (fetchLogs && detailKind.value === 'script') q.set('fetch_logs', 'true')
    execDetail.value = await http.get<ScriptExecItem>(
      `/execution-records/${detailId.value}?${q.toString()}`,
    )
    if (execDetail.value?.status === 'running') {
      startDetailPoll()
    } else {
      stopDetailPoll()
    }
  } catch (e: unknown) {
    if (!silent) snackbar(formatApiError(e, '加载详情失败'), 'error')
  } finally {
    if (!silent) detailLoading.value = false
  }
}

function startDetailPoll() {
  if (detailPollTimer) return
  detailPollTimer = setInterval(() => {
    void loadExecDetail(false, true)
  }, 3000)
}

function stopDetailPoll() {
  if (detailPollTimer) {
    clearInterval(detailPollTimer)
    detailPollTimer = null
  }
}

function toggleDetailRow(serverId: string) {
  expandedRowId.value = expandedRowId.value === serverId ? null : serverId
}

function openServerTerminal(serverId: number) {
  router.push({ path: '/terminal', query: { server_id: String(serverId) } })
}

async function stopCurrent() {
  if (!detailId.value) return
  try {
    await http.post(`/scripts/executions/${detailId.value}/stop`)
    await loadExecDetail(false)
    snackbar('已发送停止指令')
  } catch (e: unknown) {
    snackbar(formatApiError(e, '停止失败'), 'error')
  }
}

async function retryCurrent() {
  if (!detailId.value) return
  try {
    const res = await http.post<ScriptExecItem>(`/scripts/executions/${detailId.value}/retry`, {})
    const count = parseServerCount(res.server_ids)
    registerScriptExecution(
      res.id,
      `${execLabel(res)}（重试）`,
      count,
      Boolean(res.long_task),
      res.progress,
      res.status,
    )
    showScriptSubmitToast(`重试已提交 #${res.id}`)
    router.push(executionRecordPath(res.id, 'script'))
  } catch (e: unknown) {
    snackbar(formatApiError(e, '重试失败'), 'error')
  }
}

function formatFailedRowLine(row: ExecDetailRow): string {
  const domain = row.serverDomain ? ` (${row.serverDomain})` : ''
  const code = row.exitCode != null ? ` · exit ${row.exitCode}` : ''
  const line = row.summary.split('\n')[0]?.trim() || row.summary
  const summary = line && line !== '—' ? ` · ${line.slice(0, 120)}` : ''
  return `- ${row.serverName}${domain}${code}${summary}`
}

async function copyTextToClipboard(text: string, successMsg: string) {
  try {
    await navigator.clipboard.writeText(text)
    snackbar(successMsg)
  } catch {
    snackbar('复制失败', 'error')
  }
}

async function copyFailedList() {
  const failed = execDetailRows.value.filter(r => r.failed)
  if (!failed.length) return
  const lines = [
    `执行 #${detailId.value} · 失败 ${failed.length} 台`,
    ...failed.map(formatFailedRowLine),
  ]
  await copyTextToClipboard(lines.join('\n'), '已复制失败清单')
}

async function copyFailureGroupSummary() {
  const groups = execDetailFailureGroups.value
  if (!groups.length) return
  const lines = [`执行 #${detailId.value} · 失败分组 ${groups.length} 类`]
  for (const g of groups) {
    lines.push(`\n[${g.count} 台] ${g.errorKey}`)
    for (const row of g.rows.slice(0, 20)) {
      lines.push(`  - ${row.serverName}`)
    }
    if (g.rows.length > 20) lines.push(`  …及其他 ${g.rows.length - 20} 台`)
  }
  await copyTextToClipboard(lines.join('\n'), '已复制分组摘要')
}

watch(statusFilter, () => {
  page.value = 1
  loadList()
})

watch(kindFilter, () => {
  page.value = 1
  loadList()
})

watch([detailId, detailKind], ([id]) => {
  stopDetailPoll()
  execDetail.value = null
  if (id) {
    detailRowFilter.value = 'all'
    detailViewMode.value = 'list'
    void loadExecDetail(false)
  } else {
    loadList()
  }
}, { immediate: true })

useScriptExecutionListRefresh((silent) => loadList(silent), {
  enabled: () => !detailId.value,
})

const offScriptComplete = onScriptExecutionComplete(() => {
  if (detailId.value) void loadExecDetail(false, true)
  else void loadList(true)
})

let scriptRunsReady = false

onMounted(async () => {
  await loadServers({ all: true })
  scriptRunsReady = true
})

onActivated(async () => {
  if (!scriptRunsReady) return
  await loadServers({ all: true })
  if (detailId.value) await loadExecDetail(false, true)
  else await loadList(true)
})

onUnmounted(() => {
  stopDetailPoll()
  offScriptComplete()
})
</script>

<style scoped>
.exec-detail-row--failed {
  background: rgba(var(--v-theme-error), 0.06);
}
</style>
