<!--
  组件名称：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.peakTps.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" :style="{ color: rtPerfColor(report.metrics.p95ResponseTime) }">
                {{ report.metrics.p95ResponseTime.toFixed(1) }}ms
              </div>
              <div class="metric-label">P95 响应时间</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.stdDev.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" :style="{ color: apdexColor(report.metrics.apdex) }">
                {{ report.metrics.apdex.toFixed(2) }}
              </div>
              <div class="metric-label">Apdex 满意度</div>
            </el-card>
          </el-col>
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.latencyAvg.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.connectTimeAvg.toFixed(1) }}ms</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.sentKbPerSec.toFixed(1) }}</div>
              <div class="metric-label">发送速率 (KB/s)</div>
            </el-card>
          </el-col>
          <el-col :span="6">
            <el-card shadow="never" class="metric-card">
              <div class="metric-value">{{ report.metrics.receivedKbPerSec.toFixed(1) }}</div>
              <div class="metric-label">接收速率 (KB/s)</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-row :gutter="12">
          <el-col :span="12">
            <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-col>
          <el-col :span="12">
            <el-card shadow="never" class="chart-card">
              <template #header><span>错误分类统计</span></template>
              <el-table :data="errorTypeRows" size="small" v-if="hasErrorTypes">
                <el-table-column prop="label" label="错误类型" />
                <el-table-column prop="count" label="数量" width="80" />
                <el-table-column prop="percent" label="占比" width="100" />
              </el-table>
              <el-empty v-else description="无错误分类数据" :image-size="60" />
            </el-card>
          </el-col>
        </el-row>

        <!-- 状态码分布表 -->
        <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-card shadow="never" class="chart-card">
          <template #header><span>网络传输统计</span></template>
          <el-descriptions :column="4" border size="small">
            <el-descriptions-item label="总发送字节">{{ formatBytes(report.metrics.totalSentBytes) }}</el-descriptions-item>
            <el-descriptions-item label="总接收字节">{{ formatBytes(report.metrics.totalReceivedBytes) }}</el-descriptions-item>
            <el-descriptions-item label="平均发送速率">{{ report.metrics.sentKbPerSec.toFixed(1) }} KB/s</el-descriptions-item>
            <el-descriptions-item label="平均接收速率">{{ report.metrics.receivedKbPerSec.toFixed(1) }} KB/s</el-descriptions-item>
          </el-descriptions>
        </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>响应时间趋势（含 P95）</span></template>
              <div ref="rtChartRef" class="chart" />
            </el-card>
          </el-col>
        </el-row>

        <!-- 延迟分解趋势图（首字节延迟 + 连接时间） -->
        <el-row :gutter="12">
          <el-col :span="12">
            <el-card shadow="never" class="chart-card">
              <template #header><span>延迟分解趋势（首字节 / 连接）</span></template>
              <div ref="latencyChartRef" class="chart" />
            </el-card>
          </el-col>
          <el-col :span="12">
            <el-card shadow="never" class="chart-card">
              <template #header><span>带宽趋势（发送 / 接收）</span></template>
              <div ref="bandwidthChartRef" 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>()
const latencyChartRef = ref<HTMLElement>()
const bandwidthChartRef = ref<HTMLElement>()
let tpsChart: echarts.ECharts | null = null
let rtChart: echarts.ECharts | null = null
let latencyChart: echarts.ECharts | null = null
let bandwidthChart: echarts.ECharts | null = null

/** 分位数表格行（含 P95） */
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: 'P95', value: m.p95ResponseTime.toFixed(1) + ' ms' },
    { label: 'P99', value: m.p99ResponseTime.toFixed(1) + ' ms' },
  ]
})

/** 是否存在错误分类数据 */
const hasErrorTypes = computed(() => {
  if (!report.value) return false
  const m = report.value.metrics
  return (m.errorTypeTimeout || m.errorTypeConnectError || m.errorTypeClientError ||
    m.errorTypeHttp4xx || m.errorTypeHttp5xx || m.errorTypeAssertion) > 0
})

/** 错误分类表格行 */
const errorTypeRows = computed(() => {
  if (!report.value) return []
  const m = report.value.metrics
  const total = (report.value.metrics.failCount || 0) || 1
  const pct = (n: number) => ((n / total) * 100).toFixed(1) + '%'
  return [
    { label: '请求超时', count: m.errorTypeTimeout, percent: pct(m.errorTypeTimeout) },
    { label: '连接错误', count: m.errorTypeConnectError, percent: pct(m.errorTypeConnectError) },
    { label: '客户端错误（本地）', count: m.errorTypeClientError, percent: pct(m.errorTypeClientError) },
    { label: 'HTTP 4xx', count: m.errorTypeHttp4xx, percent: pct(m.errorTypeHttp4xx) },
    { label: 'HTTP 5xx', count: m.errorTypeHttp5xx, percent: pct(m.errorTypeHttp5xx) },
    { label: '断言失败', count: m.errorTypeAssertion, percent: pct(m.errorTypeAssertion) },
  ]
})

/** 响应时间性能着色：<300ms 绿 / <1000ms 黄 / 其余红（贴合市场 P95 参考线） */
function rtPerfColor(p95: number) {
  if (p95 >= 1000) return '#f56c6c'
  if (p95 >= 300) return '#e6a23c'
  return '#67c23a'
}

/** Apdex 着色：≥0.94 绿 / ≥0.85 黄 / <0.85 红 */
function apdexColor(v: number) {
  if (v >= 0.94) return '#67c23a'
  if (v >= 0.85) return '#e6a23c'
  return '#f56c6c'
}

/** 字节数人性化显示（KB/MB） */
function formatBytes(bytes: number) {
  if (!bytes) return '0 B'
  if (bytes < 1024) return bytes + ' B'
  if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
  return (bytes / 1024 / 1024).toFixed(2) + ' MB'
}

/** 状态码表格行 */
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)
  }
  if (latencyChartRef.value && !latencyChart) {
    latencyChart = echarts.init(latencyChartRef.value)
  }
  if (bandwidthChartRef.value && !bandwidthChart) {
    bandwidthChart = echarts.init(bandwidthChartRef.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 p95 = snapshots.map((s) => s.p95ResponseTime)
  const p99 = snapshots.map((s) => s.p99ResponseTime)
  const latencyAvg = snapshots.map((s) => s.latencyAvg)
  const connectTimeAvg = snapshots.map((s) => s.connectTimeAvg)
  const sentBytes = snapshots.map((s) => s.sentBytes)
  const receivedBytes = snapshots.map((s) => s.receivedBytes)

  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', 'P95', '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.2, type: 'dashed' } },
        { name: 'P90', type: 'line', data: p90, smooth: true, showSymbol: false, lineStyle: { width: 1.2, type: 'dashed' } },
        { name: 'P95', type: 'line', data: p95, smooth: true, showSymbol: false, lineStyle: { width: 2, color: '#e6a23c' } },
        { name: 'P99', type: 'line', data: p99, smooth: true, showSymbol: false, lineStyle: { width: 1.2, type: 'dotted' } },
      ],
    }
    rtChart.setOption(option, true)
  }

  if (latencyChart) {
    const option: EChartsOption = {
      tooltip: { trigger: 'axis' },
      legend: { data: ['首字节延迟', '连接时间'], 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: latencyAvg, smooth: true, showSymbol: false, areaStyle: { opacity: 0.1 } },
        { name: '连接时间', type: 'line', data: connectTimeAvg, smooth: true, showSymbol: false, lineStyle: { type: 'dashed' } },
      ],
    }
    latencyChart.setOption(option, true)
  }

  if (bandwidthChart) {
    const option: EChartsOption = {
      tooltip: { trigger: 'axis' },
      legend: { data: ['发送 KB/s', '接收 KB/s'], top: 0, itemWidth: 12, itemHeight: 8 },
      grid: { left: 50, right: 20, bottom: 30, top: 30 },
      xAxis: { type: 'category', data: labels },
      yAxis: { type: 'value', name: 'KB/s' },
      series: [
        { name: '发送 KB/s', type: 'line', data: sentBytes.map(b => +(b / 1024).toFixed(1)), smooth: true, showSymbol: false, lineStyle: { color: '#409eff' } },
        { name: '接收 KB/s', type: 'line', data: receivedBytes.map(b => +(b / 1024).toFixed(1)), smooth: true, showSymbol: false, lineStyle: { color: '#67c23a' } },
      ],
    }
    bandwidthChart.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()
  latencyChart?.dispose()
  bandwidthChart?.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>