<!--
  页面名称：Execution.vue
  页面描述：执行中心页面，实时展示测试执行状态和进度

  @author czj
  @date 2026-07-09
-->

<template>
  <div class="execution-page">
    <!-- 当前执行状态（如果有运行中的任务） -->
    <el-card v-if="activeExecution" class="current-execution" shadow="never">
      <template #header>
        <div class="card-header">
          <span>当前执行: {{ activeExecution.name }}</span>
          <el-tag :type="getStatusType(activeExecution.status)" effect="dark">
            {{ getStatusText(activeExecution.status) }}
          </el-tag>
        </div>
      </template>

      <div class="execution-progress">
        <div class="progress-stats">
          <div class="stat-item">
            <span class="stat-label">总用例</span>
            <span class="stat-value">{{ activeExecution.total_cases }}</span>
          </div>
          <div class="stat-item passed">
            <span class="stat-label">通过</span>
            <span class="stat-value">{{ activeExecution.passed }}</span>
          </div>
          <div class="stat-item failed">
            <span class="stat-label">失败</span>
            <span class="stat-value">{{ activeExecution.failed }}</span>
          </div>
          <div class="stat-item skipped">
            <span class="stat-label">跳过</span>
            <span class="stat-value">{{ activeExecution.skipped }}</span>
          </div>
          <div class="stat-item">
            <span class="stat-label">通过率</span>
            <span class="stat-value" :class="passRateClass">
              {{ activeExecution.pass_rate }}%
            </span>
          </div>
          <div class="stat-item">
            <span class="stat-label">耗时</span>
            <span class="stat-value">{{ activeExecution.duration?.toFixed(1) || 0 }}s</span>
          </div>
        </div>

        <el-progress
          :percentage="progressPercent"
          :stroke-width="20"
          :status="progressStatus"
          :striped="activeExecution.status === 'running'"
          :striped-flow="activeExecution.status === 'running'"
          :duration="8"
        >
          <span class="progress-text" :class="{ 'progress-completed': activeExecution.status !== 'running' }">
            {{ activeExecution.status === 'running' ? '执行中...' : '已完成' }}
          </span>
        </el-progress>
      </div>

      <!-- 实时步骤时间线 -->
      <div class="live-timeline" v-if="hasLiveSteps">
        <div class="timeline-title">实时步骤时间线</div>
        <el-scrollbar max-height="280px">
          <div
            v-for="(step, idx) in allLiveSteps"
            :key="`${step.case_id}-${step.step_order}-${idx}`"
            class="timeline-item"
          >
            <span class="timeline-icon">{{ getStepIcon(step.status) }}</span>
            <span class="timeline-step-name">{{ step.step_name }}</span>
            <el-tag :type="getResultType(step.status)" size="small" effect="plain">
              {{ getResultText(step.status) }}
            </el-tag>
            <span class="timeline-duration" v-if="step.duration">
              {{ step.duration.toFixed(2) }}s
            </span>
          </div>
        </el-scrollbar>
      </div>
    </el-card>

    <!-- 执行操作栏 -->
    <div class="page-header">
      <el-button type="primary" @click="showRunDialog" :disabled="selectedCases.length === 0">
        <el-icon><VideoPlay /></el-icon>
        执行选中用例 ({{ selectedCases.length }})
      </el-button>
      <el-button @click="refreshExecutions">
        <el-icon><Refresh /></el-icon>
        刷新
      </el-button>
      <el-button
        type="danger"
        plain
        @click="batchDeleteExecutions"
        :disabled="selectedExecutions.length === 0"
      >
        <el-icon><Delete /></el-icon>
        批量删除 ({{ selectedExecutions.length }})
      </el-button>
      <el-select v-model="filterStatus" placeholder="状态筛选" style="width: 150px" clearable @change="loadExecutions">
        <el-option label="全部" value="" />
        <el-option label="已完成" value="completed" />
        <el-option label="运行中" value="running" />
        <el-option label="待执行" value="pending" />
        <el-option label="已取消" value="cancelled" />
      </el-select>
    </div>

    <!-- 执行记录列表 -->
    <el-card class="execution-list" shadow="never">
      <el-table
        ref="tableRef"
        :data="executions"
        stripe
        style="width: 100%"
        v-loading="loading"
        @expand-change="handleExpandChange"
        @selection-change="handleSelectionChange"
      >
        <el-table-column type="selection" width="50" :selectable="canSelectRow" />
        <el-table-column type="expand">
          <template #default="{ row }">
            <div class="expanded-results" v-loading="resultsLoading[row.id]">
              <!-- 用例结果列表 -->
              <el-table
                :data="rowResults[row.id] || []"
                size="small"
                v-if="rowResults[row.id] && rowResults[row.id].length > 0"
              >
                <el-table-column label="用例名称" prop="case_name" min-width="200" />
                <el-table-column label="结果" width="100">
                  <template #default="{ row: r }">
                    <el-tag :type="getResultType(r.status)" size="small">
                      {{ getResultText(r.status) }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column label="耗时" width="80">
                  <template #default="{ row: r }">{{ r.duration?.toFixed(1) || 0 }}s</template>
                </el-table-column>
                <el-table-column label="步骤数" width="80" align="center">
                  <template #default="{ row: r }">
                    {{ r.steps_result?.length || 0 }}
                  </template>
                </el-table-column>
                <el-table-column label="错误信息" min-width="200">
                  <template #default="{ row: r }">
                    <span class="error-text" v-if="r.error_message">{{ r.error_message }}</span>
                    <span v-else class="no-error">-</span>
                  </template>
                </el-table-column>
                <el-table-column label="操作" width="100" fixed="right">
                  <template #default="{ row: r }">
                    <el-button
                      size="small"
                      type="primary"
                      link
                      @click="showStepDetail(row, r)"
                      v-if="r.steps_result && r.steps_result.length > 0"
                    >
                      查看步骤
                    </el-button>
                  </template>
                </el-table-column>
              </el-table>
              <el-empty v-else description="暂无执行结果" :image-size="60" />
            </div>
          </template>
        </el-table-column>

        <el-table-column prop="name" label="执行名称" min-width="200" />
        <el-table-column label="状态" width="100">
          <template #default="{ row }">
            <el-tag :type="getStatusType(row.status)" effect="plain">
              {{ getStatusText(row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="通过率" width="100">
          <template #default="{ row }">
            <span :class="getPassRateClass(row.pass_rate)">
              {{ row.pass_rate }}%
            </span>
          </template>
        </el-table-column>
        <el-table-column prop="total_cases" label="用例数" width="70" align="center" />
        <el-table-column label="耗时" width="80">
          <template #default="{ row }">{{ row.duration?.toFixed(1) || 0 }}s</template>
        </el-table-column>
        <el-table-column prop="trigger_by" label="触发人" width="100" />
        <el-table-column prop="trigger_type" label="类型" width="80">
          <template #default="{ row }">
            <el-tag v-if="row.trigger_type === 'scheduled'" type="warning" size="small">定时</el-tag>
            <el-tag v-else type="primary" size="small">手动</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="时间" width="180">
          <template #default="{ row }">
            {{ formatTime(row.created_at) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="260" fixed="right">
          <template #default="{ row }">
            <el-button
              size="small"
              type="primary"
              link
              @click="showCaseListDialog(row)"
            >
              查看详情
            </el-button>
            <el-button
              size="small"
              type="primary"
              link
              @click="toggleExpand(row)"
              v-if="row.status === 'completed'"
            >
              查看结果
            </el-button>
            <el-button
              size="small"
              type="danger"
              link
              @click="cancelExecution(row.id)"
              v-if="row.status === 'running'"
            >
              取消
            </el-button>
            <el-button
              size="small"
              type="primary"
              link
              @click="startExecution(row.id)"
              v-if="row.status === 'pending'"
            >
              执行
            </el-button>
            <el-button
              size="small"
              type="danger"
              link
              @click="deleteExecution(row)"
              v-if="row.status !== 'running'"
            >
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <div class="pagination">
        <el-pagination
          v-model:current-page="currentPage"
          v-model:page-size="pageSize"
          :total="total"
          layout="total, prev, pager, next"
          @current-change="loadExecutions"
        />
      </div>
    </el-card>

    <!-- 执行弹窗 -->
    <el-dialog v-model="runDialogVisible" title="执行测试" width="500px">
      <div class="run-dialog-content">
        <p>确定要执行以下 {{ selectedCases.length }} 个用例吗？</p>
        <el-tag v-for="id in selectedCases" :key="id" class="selected-tag" closable @close="removeCase(id)">
          {{ getCaseName(id) }}
        </el-tag>
      </div>
      <el-form label-width="80px" class="run-form">
        <el-form-item label="执行名称">
          <el-input v-model="runConfig.name" placeholder="可选，留空自动生成" />
        </el-form-item>
        <!-- UI用例显示浏览器模式选项，安全/接口测试不显示 -->
        <el-form-item label="浏览器模式" v-if="currentType === 'ui'">
          <el-switch v-model="runConfig.headless" active-text="无头" inactive-text="有头" />
        </el-form-item>
        <!-- 安全测试显示配置选择 -->
        <el-form-item label="测试配置" v-if="currentType === 'security'">
          <el-select v-model="runConfig.configId" placeholder="选择安全测试配置" style="width: 100%">
            <el-option
              v-for="cfg in securityConfigs"
              :key="cfg.id"
              :label="cfg.name"
              :value="cfg.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="执行环境" v-if="currentType === 'ui'">
          <el-select v-model="runConfig.environment" style="width: 100%">
            <el-option label="默认环境" value="default" />
            <el-option label="测试环境" value="test" />
            <el-option label="开发环境" value="dev" />
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="runDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="createAndRunExecution" :loading="executing">
          开始执行
        </el-button>
      </template>
    </el-dialog>

    <!-- 步骤详情弹窗 -->
    <el-dialog
      v-model="stepDetailVisible"
      title="步骤详情"
      width="800px"
      destroy-on-close
    >
      <StepDetailPanel
        :steps="currentStepResult"
        :case-name="currentCaseName"
        :execution-id="currentExecutionId"
      />
    </el-dialog>

    <!-- 用例列表弹窗 -->
    <el-dialog
      v-model="caseListDialogVisible"
      title="执行任务用例列表"
      width="600px"
      destroy-on-close
    >
      <div v-loading="caseListLoading">
        <el-table :data="caseListData" max-height="400px" size="small">
          <el-table-column type="index" label="#" width="50" />
          <el-table-column prop="case_name" label="用例名称" min-width="200" />
        </el-table>
        <el-empty v-if="caseListData.length === 0" description="暂无用例" :image-size="60" />
      </div>
      <template #footer>
        <el-button @click="caseListDialogVisible = false">关闭</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
/**
 * 执行中心页面
 *
 * 功能：
 * 1. 展示当前执行状态（实时进度）
 * 2. 历史执行记录列表
 * 3. 查看执行结果详情
 * 4. 查看步骤详情（截图、日志）
 * 5. 启动/取消执行
 * 6. WebSocket 实时状态推送
 */

import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { VideoPlay, Refresh, Delete } from '@element-plus/icons-vue'
import { executionApi } from '@/api/executions'
import { caseApi } from '@/api/cases'
import {
  getSecurityConfigs,
  createSecurityExecution,
  runSecurityExecution,
} from '@/api/security'
import { apiTestApi } from '@/api/apiTest'
import { WebSocketClient } from '@/utils/websocket'
import StepDetailPanel from '@/components/StepDetailPanel.vue'

// 安全测试 API 简易封装（与 executionApi 接口风格保持一致）
const securityApi = {
  create: createSecurityExecution,
  run: runSecurityExecution,
}

const route = useRoute()
const router = useRouter()

// ==================== 响应式数据 ====================

const loading = ref(false)
const executions = ref<any[]>([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
const filterStatus = ref('')

/** 当前用例类型（来自路由参数，默认 ui；接口测试模块固定为 api） */
const currentType = computed(() => {
  // 接口测试模块路由 /api-test/execution → 固定为 api
  if (route.path.startsWith('/api-test')) return 'api'
  return (route.params.type as string) || 'ui'
})

const activeExecution = ref<any>(null)
const runDialogVisible = ref(false)
const executing = ref(false)
const selectedCases = ref<string[]>([])
// 批量删除选中的执行记录
const selectedExecutions = ref<any[]>([])
const rowResults = ref<Record<string, any[]>>({})
const resultsLoading = ref<Record<string, boolean>>({})

// 用例列表缓存（用于选择用例时显示名称）
const casesCache = ref<Record<string, any>>({})

// 实时步骤时间线（按 case_id 聚合）
const liveSteps = ref<Record<string, any[]>>({})

const runConfig = ref({
  name: '',
  environment: 'default',
  headless: true,
  configId: '',  // 安全测试配置ID
})

// 安全测试配置列表
const securityConfigs = ref<any[]>([])

// 步骤详情弹窗相关
const stepDetailVisible = ref(false)
const currentStepResult = ref<any[]>([])
const currentCaseName = ref('')
const currentExecutionId = ref('')

// 用例列表弹窗相关
const caseListDialogVisible = ref(false)
const caseListLoading = ref(false)
const caseListData = ref<any[]>([])

// WebSocket 客户端
let wsClient: WebSocketClient | null = null
// 主动轮询定时器（双重保障：WebSocket 断连时仍能检测执行完成）
let activePollTimer: ReturnType<typeof setInterval> | null = null

// ==================== 计算属性 ====================

const progressPercent = computed(() => {
  if (!activeExecution.value || !activeExecution.value.total_cases) return 0
  return Math.round(
    ((activeExecution.value.passed + activeExecution.value.failed + activeExecution.value.skipped) /
      activeExecution.value.total_cases) * 100
  )
})

const progressStatus = computed(() => {
  if (!activeExecution.value) return ''
  if (activeExecution.value.status === 'running') return ''
  // 执行完成后进度条统一显示绿色
  return 'success'
})

const passRateClass = computed(() => {
  if (!activeExecution.value) return ''
  const rate = activeExecution.value.pass_rate
  if (rate >= 90) return 'rate-success'
  if (rate >= 70) return 'rate-warning'
  return 'rate-danger'
})

// 实时步骤时间线：聚合所有用例的步骤
const allLiveSteps = computed(() => {
  const all: any[] = []
  for (const cid in liveSteps.value) {
    for (const s of liveSteps.value[cid]) {
      all.push({ ...s, case_id: cid })
    }
  }
  // 按 step_order 排序
  all.sort((a, b) => (a.step_order || 0) - (b.step_order || 0))
  return all
})

const hasLiveSteps = computed(() => allLiveSteps.value.length > 0)

const getStepIcon = (status: string) => {
  const map: Record<string, string> = {
    passed: '✓',
    failed: '✗',
    running: '⏳',
    skipped: '⏭',
    pending: '⋯',
  }
  return map[status] || '•'
}

// ==================== 方法定义 ====================

const getStatusType = (status: string) => {
  const map: Record<string, string> = {
    completed: 'success',
    running: 'warning',
    failed: 'danger',
    cancelled: 'info',
    pending: 'info',
  }
  return map[status] || 'info'
}

const getStatusText = (status: string) => {
  const map: Record<string, string> = {
    completed: '已完成',
    running: '运行中',
    failed: '失败',
    cancelled: '已取消',
    pending: '待执行',
  }
  return map[status] || status
}

const getResultType = (status: string) => {
  const map: Record<string, string> = {
    passed: 'success',
    failed: 'danger',
    skipped: 'info',
    running: 'warning',
    pending: 'info',
  }
  return map[status] || 'info'
}

const getResultText = (status: string) => {
  const map: Record<string, string> = {
    passed: '通过',
    failed: '失败',
    skipped: '跳过',
    running: '运行中',
    pending: '待执行',
  }
  return map[status] || status
}

const getPassRateClass = (rate: number) => {
  if (rate >= 90) return 'rate-success'
  if (rate >= 70) return 'rate-warning'
  return 'rate-danger'
}

const formatTime = (time: string) => {
  if (!time) return '-'
  return new Date(time).toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  })
}

// 抽取的执行结果加载逻辑（供展开事件与按钮共用）
const loadExecutionResults = async (executionId: string) => {
  resultsLoading.value[executionId] = true
  try {
    const response = await executionApi.getResults(executionId)
    rowResults.value[executionId] = response.items
  } catch (error: any) {
    ElMessage.error('加载结果失败: ' + error.message)
    rowResults.value[executionId] = []
  } finally {
    resultsLoading.value[executionId] = false
  }
}

/**
 * 处理展开行事件（点击展开箭头触发）
 * 展开时自动加载执行结果，已加载的不重复请求
 */
const handleExpandChange = async (row: any, expandedRows: any[]) => {
  const isExpanded = expandedRows.some((r: any) => r.id === row.id)
  // 仅在展开且数据未加载时加载，避免重复请求
  if (isExpanded && !rowResults.value[row.id]) {
    await loadExecutionResults(row.id)
  }
}

const tableRef = ref()

const toggleExpand = (row: any) => {
  // 通过 el-table 的 toggleRowExpansion 控制展开/收起
  // 数据加载由 handleExpandChange 事件统一处理
  tableRef.value?.toggleRowExpansion(row)
}

const showStepDetail = (executionRow: any, caseResult: any) => {
  currentExecutionId.value = executionRow.id
  currentCaseName.value = caseResult.case_name
  currentStepResult.value = caseResult.steps_result || []
  stepDetailVisible.value = true
}

/**
 * 显示用例列表弹窗
 * 加载执行任务包含的所有用例名称
 */
const showCaseListDialog = async (row: any) => {
  caseListDialogVisible.value = true
  caseListLoading.value = true
  caseListData.value = []

  try {
    // 如果已经加载过结果，直接使用
    if (rowResults.value[row.id] && rowResults.value[row.id].length > 0) {
      caseListData.value = rowResults.value[row.id].map(r => ({
        case_name: r.case_name,
        case_id: r.case_id,
      }))
    } else {
      // 否则调用 API 加载
      const response = await executionApi.getResults(row.id)
      caseListData.value = response.items.map((r: any) => ({
        case_name: r.case_name,
        case_id: r.case_id,
      }))
    }
  } catch (error: any) {
    ElMessage.error('加载用例列表失败: ' + error.message)
  } finally {
    caseListLoading.value = false
  }
}

const showRunDialog = () => {
  runDialogVisible.value = true
}

const removeCase = (id: string) => {
  selectedCases.value = selectedCases.value.filter((c) => c !== id)
}

const getCaseName = (id: string) => {
  const caseInfo = casesCache.value[id]
  return caseInfo?.name || id
}

const loadExecutions = async () => {
  loading.value = true
  try {
    const response = await executionApi.list({
      skip: (currentPage.value - 1) * pageSize.value,
      limit: pageSize.value,
      status: filterStatus.value || undefined,
      case_type: currentType.value,
    })
    executions.value = response.items
    total.value = response.total

    // 检查是否有运行中的执行
    const running = executions.value.find(e => e.status === 'running')
    if (running) {
      activeExecution.value = running
      connectWebSocket(running.id)
    }
  } catch (error: any) {
    ElMessage.error('加载执行记录失败: ' + error.message)
  } finally {
    loading.value = false
  }
}

const refreshExecutions = () => {
  loadExecutions()
}

// 表格多选：运行中的记录不可勾选（避免误删正在执行的任务）
const canSelectRow = (row: any) => row.status !== 'running'
const handleSelectionChange = (selection: any[]) => {
  selectedExecutions.value = selection
}

// 删除单个执行记录（只删除执行结果，不影响测试用例）
const deleteExecution = async (row: any) => {
  try {
    await ElMessageBox.confirm(
      `确定要删除执行记录 "${row.name}" 吗？删除后无法恢复（仅删除执行结果，不影响测试用例）。`,
      '删除确认',
      { type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' }
    )
    await executionApi.delete(row.id)
    ElMessage.success('删除成功')
    await loadExecutions()
  } catch (error: any) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败: ' + (error?.message || error))
    }
  }
}

// 批量删除执行记录（运行中的自动跳过）
const batchDeleteExecutions = async () => {
  if (selectedExecutions.value.length === 0) return
  try {
    await ElMessageBox.confirm(
      `确定要删除选中的 ${selectedExecutions.value.length} 条执行记录吗？删除后无法恢复（仅删除执行结果，不影响测试用例）。`,
      '批量删除确认',
      { type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' }
    )
    const ids = selectedExecutions.value.map((e) => e.id)
    const result = await executionApi.batchDelete(ids)
    let msg = `删除完成：成功 ${result.success_count} 条`
    if (result.skipped_count > 0) {
      msg += `，跳过 ${result.skipped_count} 条（运行中）`
    }
    ElMessage.success(msg)
    await loadExecutions()
  } catch (error: any) {
    if (error !== 'cancel') {
      ElMessage.error('批量删除失败: ' + (error?.message || error))
    }
  }
}

const loadAllCases = async () => {
  try {
    const response = await caseApi.list({ limit: 500 })
    response.items.forEach((c: any) => {
      casesCache.value[c.id] = c
    })
  } catch (error: any) {
    console.error('加载用例列表失败:', error)
  }
}

// 加载安全测试配置列表
const loadSecurityConfigs = async () => {
  if (currentType.value !== 'security') return
  try {
    const response = await getSecurityConfigs(0, 100)
    const data = (response as any).data || response
    securityConfigs.value = data.items || []
    // 默认选中第一个配置
    if (securityConfigs.value.length > 0 && !runConfig.value.configId) {
      runConfig.value.configId = securityConfigs.value[0].id
    }
  } catch (error: any) {
    console.error('加载安全测试配置失败:', error)
  }
}

const createAndRunExecution = async () => {
  executing.value = true
  try {
    let execution: any

    if (currentType.value === 'security') {
      // 安全测试走 securityApi
      if (!runConfig.value.configId) {
        ElMessage.warning('请先选择安全测试配置')
        executing.value = false
        return
      }
      execution = await securityApi.create({
        configId: runConfig.value.configId,
        caseIds: selectedCases.value,
        name: runConfig.value.name || `安全测试-${new Date().toLocaleString()}`,
      })
      activeExecution.value = execution
      runDialogVisible.value = false
      selectedCases.value = []
      runConfig.value.name = ''
      runConfig.value.configId = ''

      // 触发安全测试执行
      securityApi.run(execution.id).then(() => {
        ElMessage.success('安全测试执行完成')
        loadExecutions()
      }).catch((error: any) => {
        console.warn('安全测试执行异常:', error.message)
        loadExecutions()
      })
      ElMessage.info('安全测试已启动，请等待...')
      startPolling()
      await loadExecutions()
    } else if (currentType.value === 'api') {
      // 接口测试走 apiTestApi
      const execResponse: any = await apiTestApi.createExecution({
        case_ids: selectedCases.value,
        name: runConfig.value.name || `接口测试-${new Date().toLocaleString()}`,
      })
      const execId = execResponse.execution_id
      // 获取完整执行记录
      const fullExec = await executionApi.get(execId)
      activeExecution.value = fullExec

      runDialogVisible.value = false
      selectedCases.value = []
      runConfig.value.name = ''

      // 触发接口测试执行
      apiTestApi.runExecution(execId).then(() => {
        ElMessage.success('接口测试执行完成')
        loadExecutions()
      }).catch((error: any) => {
        console.warn('接口测试执行异常:', error.message)
        loadExecutions()
      })
      ElMessage.info('接口测试已启动，请等待...')
      startPolling()
      await loadExecutions()
    } else {
      // UI用例走 executionApi
      execution = await executionApi.create({
        case_ids: selectedCases.value,
        name: runConfig.value.name,
        environment: runConfig.value.environment,
        config: { headless: runConfig.value.headless },
      })

      activeExecution.value = execution
      connectWebSocket(execution.id)
      runDialogVisible.value = false
      selectedCases.value = []
      runConfig.value.name = ''

      executionApi.run(execution.id, { headless: runConfig.value.headless }).then(() => {
        ElMessage.success('执行完成')
        loadExecutions()
      }).catch((error: any) => {
        console.warn('执行任务返回异常:', error.message)
        loadExecutions()
      })

      ElMessage.info('执行任务已启动，请等待...')
      startPolling()
      await loadExecutions()
    }
  } catch (error: any) {
    ElMessage.error('启动执行失败: ' + error.message)
  } finally {
    executing.value = false
  }
}

const startExecution = async (id: string) => {
  try {
    // 先刷新列表获取最新数据，设置当前执行
    await loadExecutions()
    const execution = executions.value.find(e => e.id === id)
    if (execution) {
      activeExecution.value = execution
      connectWebSocket(id)
    }

    // 异步触发执行（不等待完成，通过轮询/WebSocket 更新状态）
    executionApi.run(id).then(() => {
      ElMessage.success('执行完成')
      loadExecutions()
    }).catch((error: any) => {
      // 执行过程中出错不弹窗（可能是超时等），轮询会更新最终状态
      console.warn('执行任务返回异常:', error.message)
      loadExecutions()
    })

    ElMessage.info('执行任务已启动，请等待...')
    startPolling()
  } catch (error: any) {
    ElMessage.error('启动执行失败: ' + error.message)
  }
}

const cancelExecution = async (id: string) => {
  try {
    await ElMessageBox.confirm('确定要取消该执行任务吗？', '提示', {
      type: 'warning',
    })

    await executionApi.cancel(id)
    ElMessage.success('执行已取消')
    await loadExecutions()

    // 如果取消的是当前执行，断开 WebSocket
    if (activeExecution.value?.id === id) {
      disconnectWebSocket()
      activeExecution.value = null
    }
  } catch (error: any) {
    if (error !== 'cancel') {
      ElMessage.error('取消执行失败: ' + error.message)
    }
  }
}

// ==================== WebSocket 相关 ====================

const connectWebSocket = (executionId: string) => {
  // 先断开旧连接
  disconnectWebSocket()
  // 清空实时步骤时间线
  liveSteps.value = {}

  wsClient = new WebSocketClient(executionId)

  // 监听各种事件
  wsClient.on('connected', () => {
    console.log('WebSocket 已连接')
  })

  wsClient.on('execution_start', (data: any) => {
    activeExecution.value = data
  })

  wsClient.on('case_start', (data: any) => {
    console.log('用例开始:', data.case_name)
  })

  wsClient.on('step_update', (data: any) => {
    // 实时更新步骤状态 + 聚合到时间线
    console.log('步骤更新:', data.step_name, data.status)
    const cid = data.case_id
    if (!liveSteps.value[cid]) {
      liveSteps.value[cid] = []
    }
    const arr = liveSteps.value[cid]
    const idx = arr.findIndex(s => s.step_order === data.step_order)
    if (idx >= 0) {
      arr[idx] = data
    } else {
      arr.push(data)
    }
  })

  wsClient.on('case_complete', (data: any) => {
    // 更新用例结果缓存
    if (rowResults.value[activeExecution.value?.id]) {
      const results = rowResults.value[activeExecution.value?.id]
      const idx = results.findIndex(r => r.case_id === data.case_id)
      if (idx >= 0) {
        results[idx] = data
      } else {
        results.push(data)
      }
    }
    // 用例完成时合并时间线到 rowResults 的 steps_result
    const steps = liveSteps.value[data.case_id]
    if (steps && steps.length) {
      // 将时间线步骤映射回 steps_result 格式
      const mapped = steps.map(s => ({
        order: s.step_order,
        name: s.step_name,
        action: s.action,
        status: s.status,
        duration: s.duration,
        screenshot: s.screenshot,
        log: s.log,
        error: s.error,
      }))
      if (rowResults.value[activeExecution.value?.id]) {
        const results = rowResults.value[activeExecution.value?.id]
        const ridx = results.findIndex(r => r.case_id === data.case_id)
        if (ridx >= 0 && (!results[ridx].steps_result || results[ridx].steps_result.length === 0)) {
          results[ridx].steps_result = mapped
        }
      }
    }
  })

  wsClient.on('execution_complete', (data: any) => {
    activeExecution.value = data
    ElMessage.success(`执行完成: 通过率 ${data.pass_rate}%`)
    disconnectWebSocket()
    loadExecutions()
  })

  wsClient.on('execution_cancelled', (data: any) => {
    activeExecution.value = data
    disconnectWebSocket()
    loadExecutions()
  })

  wsClient.connect()

  // 启动轮询双重保障
  startPolling()
}

const disconnectWebSocket = () => {
  stopPolling()
  if (wsClient) {
    wsClient.disconnect()
    wsClient = null
  }
}

/**
 * 主动轮询当前执行状态
 *
 * 当 WebSocket 断连或 execution_complete 事件丢失时，
 * 通过定时查询 API 确保执行状态最终能被正确更新。
 */
const startPolling = () => {
  stopPolling()
  activePollTimer = setInterval(async () => {
    if (!activeExecution.value || activeExecution.value.status !== 'running') {
      stopPolling()
      return
    }
    try {
      const latest = await executionApi.get(activeExecution.value.id)
      if (latest.status !== 'running') {
        // 状态已变化，更新 activeExecution
        activeExecution.value = latest
        if (latest.status === 'completed') {
          ElMessage.success(`执行完成: 通过率 ${latest.pass_rate}%`)
        }
        disconnectWebSocket()
        await loadExecutions()
      }
    } catch (error: any) {
      console.error('轮询执行状态失败:', error)
    }
  }, 5000)
}

const stopPolling = () => {
  if (activePollTimer) {
    clearInterval(activePollTimer)
    activePollTimer = null
  }
}

// ==================== 生命周期 ====================

onMounted(async () => {
  await loadExecutions()
  loadAllCases()
  loadSecurityConfigs()

  // 检测从用例管理页跳转过来的自动执行请求
  const runId = route.query.run as string
  if (runId) {
    // 清除 query 参数，避免刷新页面重复执行
    router.replace({ path: route.path, query: {} })
    // 自动触发执行
    startExecution(runId)
  }
})

// 路由类型变化时重新加载（切换执行类型子菜单）
watch(currentType, () => {
  currentPage.value = 1
  loadExecutions()
  if (currentType.value === 'security') {
    loadSecurityConfigs()
  }
})

onUnmounted(() => {
  disconnectWebSocket()
})
</script>

<style lang="scss" scoped>
.execution-page {
  .current-execution {
    margin-bottom: 20px;

    .card-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

    .execution-progress {
      .progress-stats {
        display: flex;
        gap: 24px;
        margin-bottom: 16px;

        .stat-item {
          display: flex;
          flex-direction: column;
          align-items: center;

          .stat-label {
            font-size: 13px;
            color: #909399;
            margin-bottom: 4px;
          }

          .stat-value {
            font-size: 24px;
            font-weight: 600;
          }

          &.passed .stat-value { color: #67c23a; }
          &.failed .stat-value { color: #f56c6c; }
          &.skipped .stat-value { color: #909399; }
        }
      }

      .progress-text {
        font-size: 14px;

        &.progress-completed {
          color: #67c23a;
          font-weight: 600;
        }
      }
    }

    .live-timeline {
      margin-top: 16px;
      padding-top: 12px;
      border-top: 1px dashed #ebeef5;

      .timeline-title {
        font-size: 14px;
        font-weight: 600;
        color: #303133;
        margin-bottom: 8px;
      }

      .timeline-item {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 6px 0;
        font-size: 13px;
        border-bottom: 1px dashed #f0f0f0;

        .timeline-icon {
          width: 18px;
          text-align: center;
          font-weight: 600;
        }

        .timeline-step-name {
          flex: 1;
          color: #303133;
        }

        .timeline-duration {
          color: #909399;
          font-size: 12px;
        }
      }
    }
  }

  .page-header {
    margin-bottom: 20px;
    display: flex;
    gap: 12px;
    align-items: center;
  }

  .execution-list {
    .rate-success { color: #67c23a; font-weight: 600; }
    .rate-warning { color: #e6a23c; font-weight: 600; }
    .rate-danger { color: #f56c6c; font-weight: 600; }

    .error-text {
      color: #f56c6c;
      font-size: 13px;
    }

    .no-error {
      color: #909399;
    }

    .expanded-results {
      padding: 12px 40px;
      background: #fafafa;
    }

    .pagination {
      margin-top: 20px;
      display: flex;
      justify-content: center;
    }
  }

  .run-dialog-content {
    margin-bottom: 20px;

    .selected-tag {
      margin: 4px;
    }
  }
}
</style>