<!--
  组件名称：ReportPanel.vue
  组件描述：性能测试报告查看面板 - 摘要信息 / 指标统计 / 快照趋势图

  @author czj
  @date 2026-08-12
-->

<template>
  <div class="report-panel">
    <!-- 无任务时提示 → 提供任务选择器 -->
    <div v-if="!taskId" class="empty-state">
      <el-icon :size="48"><Document /></el-icon>
      <p style="margin-bottom: 16px;">请选择要查看报告的任务</p>
      <el-select
        v-model="selectedTaskId"
        placeholder="选择任务"
        filterable
        clearable
        style="width: 360px"
        @change="onTaskSelected"
      >
        <el-option
          v-for="t in availableTasks"
          :key="t.id"
          :label="`${t.name} (${t.id.slice(0, 8)}...)`"
          :value="t.id"
        />
      </el-select>
      <el-button
        v-if="selectedTaskId"
        type="primary"
        style="margin-left: 12px;"
        @click="onTaskSelected(selectedTaskId)"
      >查看报告</el-button>
      <p v-if="tasksLoading" style="margin-top: 12px; color: #909399; font-size: 13px;">
        加载中...
      </p>
    </div>

    <template v-else>
      <div class="report-header">
        <el-button type="primary" :loading="loading" @click="loadReport">
          <el-icon><Refresh /></el-icon>刷新报告
        </el-button>
        <el-button v-if="report" @click="exportReport">
          <el-icon><Download /></el-icon>导出 JSON
        </el-button>
      </div>

      <!-- 加载中 -->
      <el-skeleton v-if="loading" :rows="8" animated />

      <template v-else-if="report">
        <!-- 摘要信息 -->
        <el-descriptions title="执行摘要" :column="3" border class="summary-block">
          <el-descriptions-item label="任务名称">{{ report.summary.taskName }}</el-descriptions-item>
          <el-descriptions-item label="目标 URL">{{ report.summary.targetUrl }}</el-descriptions-item>
          <el-descriptions-item label="HTTP 方法">{{ report.summary.method }}</el-descriptions-item>
          <el-descriptions-item label="压测模式">{{ modeLabel(report.summary.mode) }}</el-descriptions-item>
          <el-descriptions-item label="并发数">{{ report.summary.concurrency }}</el-descriptions-item>
          <el-descriptions-item label="账号">{{ report.summary.accountKey || '-' }}</el-descriptions-item>
          <el-descriptions-item label="开始时间">{{ formatTime(report.summary.startTime) }}</el-descriptions-item>
          <el-descriptions-item label="结束时间">{{ formatTime(report.summary.endTime) }}</el-descriptions-item>
          <el-descriptions-item label="实际时长">{{ report.summary.durationActual != null ? report.summary.durationActual.toFixed(1) + 's' : '-' }}</el-descriptions-item>
        </el-descriptions>

        <!-- 核心指标卡片 -->
        <el-row :gutter="12" class="metrics-row">
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.totalRequests }}</div>
              <div class="metric-label">总请求数</div>
            </el-card>
          </el-col>
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.successCount }}</div>
              <div class="metric-label">成功数</div>
            </el-card>
          </el-col>
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.failCount }}</div>
              <div class="metric-label">失败数</div>
            </el-card>
          </el-col>
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value" :style="{ color: report.metrics.errorRate > 5 ? '#f56c6c' : '#67c23a' }">
                {{ (report.metrics.errorRate * 100).toFixed(2) }}%
              </div>
              <div class="metric-label">错误率</div>
            </el-card>
          </el-col>
        </el-row>

        <el-row :gutter="12" class="metrics-row">
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.actualTps.toFixed(1) }}</div>
              <div class="metric-label">实际 TPS</div>
            </el-card>
          </el-col>
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.avgResponseTime.toFixed(1) }}ms</div>
              <div class="metric-label">平均响应时间</div>
            </el-card>
          </el-col>
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.minResponseTime.toFixed(1) }}ms</div>
              <div class="metric-label">最小响应时间</div>
            </el-card>
          </el-col>
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.maxResponseTime.toFixed(1) }}ms</div>
              <div class="metric-label">最大响应时间</div>
            </el-card>
          </el-col>
        </el-row>

        <!-- 响应时间分位数 -->
        <el-card shadow="never" class="chart-card">
          <template #header><span>响应时间摘要</span></template>
          <el-table :data="quantileRows" size="small">
            <el-table-column prop="label" label="分位数" width="120" />
            <el-table-column prop="value" label="值" />
          </el-table>
        </el-card>

        <!-- 状态码分布表 -->
        <el-card shadow="never" class="chart-card">
          <template #header><span>状态码分布</span></template>
          <el-table :data="statusRows" size="small">
            <el-table-column prop="code" label="状态码" width="120" />
            <el-table-column prop="count" label="数量" />
            <el-table-column prop="percent" label="占比" />
          </el-table>
        </el-card>

        <!-- 快照趋势图 -->
        <el-row :gutter="12">
          <el-col :span="12">
            <el-card shadow="never" class="chart-card">
              <template #header><span>TPS 趋势</span></template>
              <div ref="tpsChartRef" class="chart" />
            </el-card>
          </el-col>
          <el-col :span="12">
            <el-card shadow="never" class="chart-card">
              <template #header><span>响应时间趋势</span></template>
              <div ref="rtChartRef" class="chart" />
            </el-card>
          </el-col>
        </el-row>
      </template>

      <el-empty v-else description="暂无报告数据（任务尚未执行）" />
    </template>
  </div>
</template>

<script setup lang="ts">
/**
 * 性能测试报告查看面板
 *
 * 功能：加载任务报告，展示摘要、指标、状态码分布和趋势图
 */

import { ref, watch, onMounted, onUnmounted, nextTick, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Document, Refresh, Download } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { getReport, listTasks } from '@/api/performance'
import type { PerformanceReportResponse } from '@/types/performance'
import type { EChartsOption } from 'echarts'
import type { PerformanceTaskListItem } from '@/types/performance'

const route = useRoute()
const router = useRouter()
/** 从路由 query 读取任务 ID（由任务列表报告按钮跳转携带） */
const taskId = computed(() => (route.query.taskId as string) || '')

/** 任务选择器状态 */
const selectedTaskId = ref('')
const availableTasks = ref<PerformanceTaskListItem[]>([])
const tasksLoading = ref(false)

/** 加载可选任务列表（已完成/失败的任务才能查看报告） */
async function loadAvailableTasks() {
  tasksLoading.value = true
  try {
    const res = await listTasks({ page: 1, pageSize: 100 })
    availableTasks.value = (res.items || []).filter(
      t => t.status === 'completed' || t.status === 'failed'
    )
  } catch { /* ignore */ } finally {
    tasksLoading.value = false
  }
}

/** 选中任务后跳转 */
function onTaskSelected(id: string) {
  if (id) {
    router.push({ path: '/performance/report', query: { taskId: id } })
  }
}

const loading = ref(false)
const report = ref<PerformanceReportResponse | null>(null)

const tpsChartRef = ref<HTMLElement>()
const rtChartRef = ref<HTMLElement>()
let tpsChart: echarts.ECharts | null = null
let rtChart: echarts.ECharts | null = null

/** 分位数表格行 */
const quantileRows = computed(() => {
  if (!report.value) return []
  const m = report.value.metrics
  return [
    { label: 'P50（中位数）', value: m.p50ResponseTime.toFixed(1) + ' ms' },
    { label: 'P90', value: m.p90ResponseTime.toFixed(1) + ' ms' },
    { label: 'P99', value: m.p99ResponseTime.toFixed(1) + ' ms' },
  ]
})

/** 状态码表格行 */
const statusRows = computed(() => {
  if (!report.value) return []
  const m = report.value.metrics
  const total = m.totalRequests || 1
  const rows = [
    { code: '2xx', count: m.status2xx, percent: ((m.status2xx / total) * 100).toFixed(1) + '%' },
    { code: '3xx', count: m.status3xx, percent: ((m.status3xx / total) * 100).toFixed(1) + '%' },
    { code: '4xx', count: m.status4xx, percent: ((m.status4xx / total) * 100).toFixed(1) + '%' },
    { code: '5xx', count: m.status5xx, percent: ((m.status5xx / total) * 100).toFixed(1) + '%' },
  ]
  return rows
})

/** 加载报告 */
async function loadReport() {
  if (!taskId.value) return
  loading.value = true
  try {
    const res = await getReport(taskId.value)
    report.value = res
    // 先隐藏骨架屏让图表 div 渲染到 DOM，再初始化图表（v-if/v-else-if 互斥，否则 ref 为 undefined）
    loading.value = false
    await nextTick()
    initCharts()
    updateCharts()
  } catch (e: any) {
    ElMessage.error('加载报告失败: ' + (e.response?.data?.detail || e.message || ''))
    loading.value = false
  }
}

/** 导出报告 JSON */
function exportReport() {
  if (!report.value) return
  const blob = new Blob([JSON.stringify(report.value, null, 2)], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `perf-report-${report.value.summary.taskId}.json`
  a.click()
  URL.revokeObjectURL(url)
}

/** 初始化图表 */
function initCharts() {
  if (tpsChartRef.value && !tpsChart) {
    tpsChart = echarts.init(tpsChartRef.value)
  }
  if (rtChartRef.value && !rtChart) {
    rtChart = echarts.init(rtChartRef.value)
  }
}

/** 更新图表 */
function updateCharts() {
  if (!report.value || !report.value.snapshots.length) return
  const snapshots = report.value.snapshots
  const labels = snapshots.map((s) => `${s.elapsed.toFixed(0)}s`)
  const tps = snapshots.map((s) => s.tps)
  const avg = snapshots.map((s) => s.avgResponseTime)
  const p50 = snapshots.map((s) => s.p50ResponseTime)
  const p90 = snapshots.map((s) => s.p90ResponseTime)
  const p99 = snapshots.map((s) => s.p99ResponseTime)

  if (tpsChart) {
    const option: EChartsOption = {
      tooltip: { trigger: 'axis' },
      grid: { left: 50, right: 20, bottom: 30, top: 10 },
      xAxis: { type: 'category', data: labels },
      yAxis: { type: 'value', name: 'TPS' },
      series: [{
        type: 'line',
        data: tps,
        smooth: true,
        showSymbol: false,
        areaStyle: {
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: 'rgba(64,158,255,0.3)' },
            { offset: 1, color: 'rgba(64,158,255,0.05)' },
          ]),
        },
      }],
    }
    tpsChart.setOption(option, true)
  }

  if (rtChart) {
    const option: EChartsOption = {
      tooltip: { trigger: 'axis' },
      legend: { data: ['平均', 'P50', 'P90', 'P99'], top: 0, itemWidth: 12, itemHeight: 8 },
      grid: { left: 50, right: 20, bottom: 30, top: 30 },
      xAxis: { type: 'category', data: labels },
      yAxis: { type: 'value', name: 'ms' },
      series: [
        { name: '平均', type: 'line', data: avg, smooth: true, showSymbol: false, lineStyle: { width: 1.5 } },
        { name: 'P50', type: 'line', data: p50, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed' } },
        { name: 'P90', type: 'line', data: p90, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed' } },
        { name: 'P99', type: 'line', data: p99, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dotted' } },
      ],
    }
    rtChart.setOption(option, true)
  }
}

/** 模式标签 */
function modeLabel(mode: string) {
  const map: Record<string, string> = { concurrent: '并发', qps: 'QPS', step: '阶梯' }
  return map[mode] || mode
}

/** 格式化时间 */
function formatTime(t: string | null | undefined) {
  return t?.replace('T', ' ').substring(0, 19) || '-'
}

defineExpose({ loadReport })

watch(() => route.query.taskId, (newId) => {
  if (newId) {
    loadReport()
  }
})

onMounted(() => {
  if (taskId.value) {
    loadReport()
  } else {
    loadAvailableTasks()
  }
})

onUnmounted(() => {
  tpsChart?.dispose()
  rtChart?.dispose()
})
</script>

<style scoped>
.report-panel {
  padding: 0;
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 80px 0;
  color: #909399;
}

.empty-state p {
  margin-top: 12px;
  font-size: 14px;
}

.report-header {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-bottom: 12px;
}

.summary-block {
  margin-bottom: 12px;
}

.metrics-row {
  margin-bottom: 12px;
}

.metric-card {
  text-align: center;
}

.metric-value {
  font-size: 26px;
  font-weight: 700;
  color: #409eff;
  line-height: 1.2;
}

.metric-label {
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}

.chart-card {
  margin-bottom: 12px;
}

.chart {
  width: 100%;
  height: 280px;
}
</style>