<!--
  组件名称：MonitorPanel.vue
  组件描述：性能测试实时监控面板 - TPS/响应时间/状态码实时图表

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

<template>
  <div class="monitor-panel">
    <!-- 无任务时提示 → 提供任务选择器 -->
    <div v-if="!taskId" class="empty-state">
      <el-icon :size="48"><Monitor /></el-icon>
      <p style="margin-bottom: 16px;">请选择要查看实时监控的任务</p>
      <el-select
        v-model="selectedTaskId"
        placeholder="选择任务 ID"
        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="monitor-header">
        <div class="task-info">
          <span class="label">任务ID：</span><span class="value">{{ taskId }}</span>
        </div>
        <div class="status-bar">
          <span class="status-dot" :class="connectionStatus" />
          <span>{{ connectionStatusText }}</span>
          <el-tag v-if="taskStatus" :type="statusTagType" size="small" style="margin-left: 8px">
            {{ statusLabel(taskStatus) }}
          </el-tag>
        </div>
      </div>

      <!-- 实时指标卡片 -->
      <el-row :gutter="12" class="metrics-row">
        <el-col :span="6">
          <el-card shadow="never" class="metric-card">
            <div class="metric-value">{{ currentTps.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">{{ currentAvgRt.toFixed(0) }}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">{{ 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" :style="{ color: errorRate > 5 ? '#f56c6c' : '#67c23a' }">
              {{ errorRate.toFixed(1) }}%
            </div>
            <div class="metric-label">错误率</div>
          </el-card>
        </el-col>
      </el-row>

      <!-- TPS 趋势图 -->
      <el-card shadow="never" class="chart-card">
        <template #header><span>TPS 趋势</span></template>
        <div ref="tpsChartRef" class="chart" />
      </el-card>

      <!-- 响应时间图 + 状态码分布 -->
      <el-row :gutter="12">
        <el-col :span="16">
          <el-card shadow="never" class="chart-card">
            <template #header><span>响应时间趋势</span></template>
            <div ref="rtChartRef" class="chart" />
          </el-card>
        </el-col>
        <el-col :span="8">
          <el-card shadow="never" class="chart-card">
            <template #header><span>状态码分布</span></template>
            <div ref="statusChartRef" class="chart" />
          </el-card>
        </el-col>
      </el-row>
    </template>
  </div>
</template>

<script setup lang="ts">
/**
 * 性能测试实时监控面板
 *
 * 功能：WebSocket 实时接收快照数据，ECharts 动态展示 TPS/响应时间/状态码
 */

import { ref, watch, onMounted, onUnmounted, nextTick, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Monitor } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { createPerfMonitorStream, listTasks } from '@/api/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 === 'running' || t.status === 'completed' || t.status === 'failed'
    )
  } catch { /* ignore */ } finally {
    tasksLoading.value = false
  }
}

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

// 连接状态
const connectionStatus = ref<'disconnected' | 'connecting' | 'connected'>('disconnected')
const connectionStatusText = computed(() => {
  const map = { disconnected: '未连接', connecting: '连接中...', connected: '已连接' }
  return map[connectionStatus.value]
})
const taskStatus = ref<string>('')

// 实时指标
const currentTps = ref(0)
const currentAvgRt = ref(0)
const totalRequests = ref(0)
const errorRate = ref(0)

// 图表数据缓冲区
const timeLabels = ref<string[]>([])
const tpsData = ref<number[]>([])
const avgRtData = ref<number[]>([])
const p50Data = ref<number[]>([])
const p90Data = ref<number[]>([])
const p99Data = ref<number[]>([])
const status2xx = ref(0)
const status4xx = ref(0)
const status5xx = ref(0)

// 图表引用
const tpsChartRef = ref<HTMLElement>()
const rtChartRef = ref<HTMLElement>()
const statusChartRef = ref<HTMLElement>()

let tpsChart: echarts.ECharts | null = null
let rtChart: echarts.ECharts | null = null
let statusChart: echarts.ECharts | null = null
let ws: WebSocket | null = null

/** 状态标签类型 */
function statusTagType(status: string) {
  const map: Record<string, string> = { completed: 'success', running: 'primary', failed: 'danger', cancelled: 'warning' }
  return map[status] || 'info'
}

/** 状态标签文字 */
function statusLabel(status: string) {
  const map: Record<string, string> = { completed: '已完成', running: '运行中', pending: '等待中', failed: '失败', cancelled: '已取消' }
  return map[status] || status
}

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

/** 更新 TPS 图表 */
function updateTpsChart() {
  if (!tpsChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'axis' },
    grid: { left: 50, right: 20, bottom: 30, top: 10 },
    xAxis: { type: 'category', data: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'TPS' },
    series: [{
      type: 'line',
      data: tpsData.value,
      smooth: true,
      showSymbol: false,
      lineStyle: { width: 2 },
      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)
}

/** 更新响应时间图表 */
function updateRtChart() {
  if (!rtChart) return
  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: timeLabels.value, axisLabel: { fontSize: 11 } },
    yAxis: { type: 'value', name: 'ms' },
    series: [
      { name: '平均', type: 'line', data: avgRtData.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5 } },
      { name: 'P50', type: 'line', data: p50Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed' } },
      { name: 'P90', type: 'line', data: p90Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dashed' } },
      { name: 'P99', type: 'line', data: p99Data.value, smooth: true, showSymbol: false, lineStyle: { width: 1.5, type: 'dotted' } },
    ],
  }
  rtChart.setOption(option, true)
}

/** 更新状态码饼图 */
function updateStatusChart() {
  if (!statusChart) return
  const option: EChartsOption = {
    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
    series: [{
      type: 'pie',
      radius: ['40%', '70%'],
      center: ['50%', '55%'],
      data: [
        { name: '2xx', value: status2xx.value, itemStyle: { color: '#67c23a' } },
        { name: '4xx', value: status4xx.value, itemStyle: { color: '#e6a23c' } },
        { name: '5xx', value: status5xx.value, itemStyle: { color: '#f56c6c' } },
      ],
      label: { show: true, formatter: '{b}\n{d}%' },
    }],
  }
  statusChart.setOption(option, true)
}

/** 处理快照数据 */
function handleSnapshot(data: any) {
  const d = data.data || data
  currentTps.value = d.tps || 0
  currentAvgRt.value = d.avgResponseTime || d.avg_response_time || 0
  totalRequests.value = d.totalRequests || d.total_requests || 0
  const fail = d.failCount || d.fail_count || 0
  const total = totalRequests.value
  errorRate.value = total > 0 ? (fail / total) * 100 : 0

  // 更新图表数据
  const elapsed = d.elapsed != null ? `${d.elapsed.toFixed(0)}s` : `${timeLabels.value.length + 1}s`
  timeLabels.value.push(elapsed)
  tpsData.value.push(d.tps || 0)
  avgRtData.value.push(d.avgResponseTime || d.avg_response_time || 0)
  p50Data.value.push(d.p50 || 0)
  p90Data.value.push(d.p90 || 0)
  p99Data.value.push(d.p99 || 0)

  status2xx.value += d.status2xx || d.status_2xx || 0
  status4xx.value += d.status4xx || d.status_4xx || 0
  status5xx.value += d.status5xx || d.status_5xx || 0

  // 限制显示点数
  if (timeLabels.value.length > 300) {
    timeLabels.value.shift()
    tpsData.value.shift()
    avgRtData.value.shift()
    p50Data.value.shift()
    p90Data.value.shift()
    p99Data.value.shift()
  }

  // 更新图表
  updateTpsChart()
  updateRtChart()
  updateStatusChart()
  taskStatus.value = 'running'
}

/** 处理完成事件 */
function handleComplete(data: any) {
  taskStatus.value = data.status || 'completed'
  connectionStatus.value = 'disconnected'
  ElMessage.success('性能测试已完成')
}

/** 连接 WebSocket */
function connectWs() {
  if (!taskId.value) return

  // 关闭旧连接
  disconnectWs()

  // 重置数据
  timeLabels.value = []
  tpsData.value = []
  avgRtData.value = []
  p50Data.value = []
  p90Data.value = []
  p99Data.value = []
  status2xx.value = 0
  status4xx.value = 0
  status5xx.value = 0
  currentTps.value = 0
  currentAvgRt.value = 0
  totalRequests.value = 0
  errorRate.value = 0
  taskStatus.value = ''

  connectionStatus.value = 'connecting'

  ws = createPerfMonitorStream(
    taskId.value,
    handleSnapshot,
    handleComplete,
    () => {
      connectionStatus.value = 'disconnected'
      ElMessage.error('WebSocket 连接异常')
    },
    () => {
      if (connectionStatus.value === 'connected') {
        connectionStatus.value = 'disconnected'
      }
    },
  )

  ws.onopen = () => {
    connectionStatus.value = 'connected'
  }
}

/** 断开 WebSocket */
function disconnectWs() {
  if (ws) {
    ws.close()
    ws = null
  }
}

/** 刷新（外部调用） */
function refresh() {
  connectWs()
}

defineExpose({ refresh })

watch(() => route.query.taskId, (newId) => {
  if (newId) {
    nextTick(() => {
      initCharts()
      connectWs()
    })
  }
})

onMounted(() => {
  if (taskId.value) {
    nextTick(() => {
      initCharts()
      connectWs()
    })
  } else {
    // 无任务时加载可选项，方便从侧边栏直接进入
    loadAvailableTasks()
  }
})

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

<style scoped>
.monitor-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;
}

.monitor-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
  font-size: 13px;
}

.task-info .label {
  color: #909399;
}

.task-info .value {
  color: #303133;
  font-family: monospace;
}

.status-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: #909399;
}

.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
}

.status-dot.connected {
  background: #67c23a;
}

.status-dot.connecting {
  background: #e6a23c;
  animation: pulse 1s infinite;
}

.status-dot.disconnected {
  background: #c0c4cc;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

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

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

.metric-value {
  font-size: 28px;
  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>