<!--
  页面名称：ApiCaseList.vue
  页面描述：接口测试用例管理页面 — 用例列表 + 请求编辑器 + 断言配置

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

<template>
  <div class="api-case-page">
    <!-- 页面头部 -->
    <div class="page-header">
      <div class="header-left">
        <el-tree-select
          v-model="filterModuleId"
          :data="moduleTreeData"
          :props="{ label: 'name', value: 'id', children: 'children' }"
          placeholder="选择模块"
          clearable
          check-strictly
          filterable
          style="width: 200px"
          @change="loadCases"
        />
        <el-input
          v-model="keyword"
          placeholder="搜索用例名称..."
          clearable
          style="width: 200px"
          @input="loadCases"
        >
          <template #prefix><el-icon><Search /></el-icon></template>
        </el-input>
        <el-select v-model="filterMethod" placeholder="请求方法" clearable style="width: 120px" @change="loadCases">
          <el-option label="GET" value="GET" />
          <el-option label="POST" value="POST" />
          <el-option label="PUT" value="PUT" />
          <el-option label="DELETE" value="DELETE" />
          <el-option label="PATCH" value="PATCH" />
        </el-select>
      </div>
      <div class="header-right">
        <el-button type="primary" @click="openCreateDialog">
          <el-icon><Plus /></el-icon> 新建用例
        </el-button>
      </div>
    </div>

    <!-- 用例表格 -->
    <el-card shadow="never">
      <el-table :data="cases" v-loading="loading" stripe style="width: 100%" @row-dblclick="viewCaseDetail">
        <el-table-column label="用例名称" min-width="200">
          <template #default="{ row }">
            <span class="case-name">{{ row.name }}</span>
          </template>
        </el-table-column>
        <el-table-column label="方法" width="90">
          <template #default="{ row }">
            <el-tag :type="methodTagType(getSteps(row).method)" size="small" effect="dark">
              {{ getSteps(row).method || 'GET' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="接口路径" min-width="280">
          <template #default="{ row }">
            <code class="api-path">{{ getSteps(row).url || '-' }}</code>
          </template>
        </el-table-column>
        <el-table-column label="所属模块" width="140" show-overflow-tooltip>
          <template #default="{ row }">
            {{ getModuleName(row.module_id) }}
          </template>
        </el-table-column>
        <el-table-column label="断言数" width="80">
          <template #default="{ row }">
            <el-tag size="small" round>{{ getAssertionCount(row) }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="状态" width="80">
          <template #default="{ row }">
            <el-tag :type="row.status === 'active' ? 'success' : 'info'" size="small">
              {{ row.status === 'active' ? '启用' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="220" fixed="right">
          <template #default="{ row }">
            <el-button size="small" @click="openEditDialog(row)">编辑</el-button>
            <el-button size="small" type="success" @click="executeSingle(row)">执行</el-button>
            <el-button size="small" type="danger" link @click="deleteCase(row)">删除</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, sizes"
          :page-sizes="[10, 20, 50, 100]"
          @current-change="loadCases"
          @size-change="loadCases"
        />
      </div>
    </el-card>

    <!-- 最近执行记录 -->
    <el-card shadow="never" style="margin-top: 16px;">
      <template #header>
        <div style="display:flex;justify-content:space-between;align-items:center;">
          <span>最近执行记录</span>
          <el-button size="small" @click="$router.push('/execution/api')">查看全部</el-button>
        </div>
      </template>
      <el-table :data="recentExecutions" v-loading="loadingExec" stripe style="width: 100%">
        <el-table-column prop="name" label="执行名称" min-width="160" />
        <el-table-column label="执行结果" width="200">
          <template #default="{ row }">
            <span class="exec-stats">
              <span class="stat passed">{{ row.passed }}</span>/
              <span class="stat failed">{{ row.failed }}</span>/
              <span class="stat">{{ row.total_cases }}</span>
            </span>
            <el-progress
              :percentage="row.pass_rate || 0"
              :status="row.pass_rate >= 100 ? 'success' : row.pass_rate > 0 ? 'warning' : 'exception'"
              :stroke-width="8"
              style="width: 120px; display: inline-block; margin-left: 8px;"
            />
          </template>
        </el-table-column>
        <el-table-column label="状态" width="90">
          <template #default="{ row }">
            <el-tag :type="statusTagType(row.status)" size="small">{{ row.status }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="执行时间" width="170">
          <template #default="{ row }">{{ row.created_at }}</template>
        </el-table-column>
      </el-table>
    </el-card>

    <!-- 新建/编辑用例弹窗 -->
    <el-dialog
      v-model="editDialogVisible"
      :title="editMode === 'create' ? '新建接口测试用例' : '编辑接口测试用例'"
      width="860px"
      destroy-on-close
      top="5vh"
    >
      <el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="100px">
        <!-- 基本信息 -->
        <el-row :gutter="16">
          <el-col :span="16">
            <el-form-item label="用例名称" prop="name">
              <el-input v-model="editForm.name" placeholder="如：创建会议-messageName为空边界测试" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="所属模块" prop="module_id">
              <el-tree-select
                v-model="editForm.module_id"
                :data="moduleTreeData"
                :props="{ label: 'name', value: 'id', children: 'children' }"
                placeholder="选择模块"
                check-strictly
                filterable
                clearable
              />
            </el-form-item>
          </el-col>
        </el-row>

        <el-divider content-position="left">
          <el-icon><Setting /></el-icon> HTTP 请求配置
        </el-divider>

        <!-- 请求方法 + URL -->
        <el-row :gutter="16">
          <el-col :span="4">
            <el-form-item label="请求方法" prop="steps.method">
              <el-select v-model="apiConfig.method" style="width: 100%">
                <el-option label="GET" value="GET" />
                <el-option label="POST" value="POST" />
                <el-option label="PUT" value="PUT" />
                <el-option label="DELETE" value="DELETE" />
                <el-option label="PATCH" value="PATCH" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="20">
            <el-form-item label="接口路径" prop="steps.url">
              <el-input v-model="apiConfig.url" placeholder="/meetingV3/api/message/book" style="font-family: monospace;">
                <template #prefix>
                  <el-tag :type="methodTagType(apiConfig.method)" size="small" effect="dark" style="margin-right: 4px;">
                    {{ apiConfig.method }}
                  </el-tag>
                </template>
              </el-input>
            </el-form-item>
          </el-col>
        </el-row>

        <!-- 变量提示 -->
        <div class="variable-hint">
          支持变量占位符：
          <code>{__RANDOM_NAME_8__}</code>
          <code>{__NOW__}</code>
          <code>{__NOW_+2_H__}</code>
          <code>{__UUID__}</code>
          <code>{__RANDOM_INT_1_100__}</code>
        </div>

        <el-divider content-position="left">
          <el-icon><Lock /></el-icon> 认证配置
        </el-divider>

        <el-row :gutter="16">
          <el-col :span="6">
            <el-form-item label="登录 Token">
              <el-switch v-model="apiConfig.auth_required" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="接口签名">
              <el-switch v-model="apiConfig.sign_request" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="使用账号">
              <el-select v-model="apiConfig.account_key" :disabled="!apiConfig.auth_required">
                <el-option label="superadmin" value="superadmin" />
                <el-option label="admin" value="admin" />
                <el-option label="user" value="user" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="预期说明">
              <el-input v-model="apiConfig.description" placeholder="预期结果描述" />
            </el-form-item>
          </el-col>
        </el-row>

        <el-divider content-position="left">
          <el-icon><Document /></el-icon> 请求头 (Headers)
        </el-divider>

        <div class="kv-editor">
          <div class="kv-row header">
            <span class="kv-key">Key</span>
            <span class="kv-val">Value</span>
            <span class="kv-act">操作</span>
          </div>
          <div class="kv-row" v-for="(h, i) in apiConfig.headers" :key="i">
            <div class="kv-key">
              <el-input v-model="h.key" placeholder="Header Key" size="small" />
            </div>
            <div class="kv-val">
              <el-input v-model="h.value" placeholder="Header Value" size="small" />
            </div>
            <div class="kv-act">
              <el-button link type="danger" size="small" @click="apiConfig.headers.splice(i, 1)">×</el-button>
            </div>
          </div>
          <div class="kv-add" @click="apiConfig.headers.push({ key: '', value: '' })">
            ＋ 添加请求头
          </div>
        </div>

        <el-divider content-position="left">
          <el-icon><Box /></el-icon> 请求体 (Body)
          <span class="desc">JSON 格式，支持模板变量</span>
        </el-divider>

        <el-form-item label="请求体">
          <el-input
            v-model="apiConfig.bodyText"
            type="textarea"
            :rows="8"
            placeholder='{
  "messageName": "测试会议_{__RANDOM_NAME_8__}",
  "startTime": "{__NOW__}",
  "endTime": "{__NOW_+2_H__}"
}'
            style="font-family: 'Courier New', monospace; font-size: 12px;"
          />
        </el-form-item>

        <el-divider content-position="left">
          <el-icon><Select /></el-icon> 断言规则
          <span class="desc">全部通过才算执行成功</span>
        </el-divider>

        <div class="assertion-list">
          <div class="assertion-card" v-for="(a, i) in apiConfig.assertions" :key="i">
            <el-button link type="danger" size="small" class="remove-btn" @click="apiConfig.assertions.splice(i, 1)">
              <el-icon><Delete /></el-icon>
            </el-button>
            <el-row :gutter="8">
              <el-col :span="6">
                <el-select v-model="a.type" style="width: 100%">
                  <el-option label="状态码 (status_code)" value="status_code" />
                  <el-option label="响应体字段 (body_field)" value="body_field" />
                  <el-option label="响应时间 (response_time)" value="response_time" />
                  <el-option label="响应头 (header)" value="header" />
                </el-select>
              </el-col>
              <el-col :span="6">
                <el-select v-model="a.operator" style="width: 100%">
                  <el-option label="等于 (equals)" value="equals" />
                  <el-option label="不等于 (not_equals)" value="not_equals" />
                  <el-option label="包含 (contains)" value="contains" />
                  <el-option label="正则匹配 (regex)" value="regex" />
                  <el-option label="大于 (greater_than)" value="greater_than" />
                  <el-option label="小于 (less_than)" value="less_than" />
                </el-select>
              </el-col>
              <el-col :span="6">
                <el-input v-model="a.value" placeholder="期望值" />
              </el-col>
              <el-col :span="6">
                <el-input v-model="a.path" placeholder="JSONPath (body_field 必填)" v-if="a.type === 'body_field'" />
                <el-input v-model="a.path" placeholder="Header 名 (header 必填)" v-else-if="a.type === 'header'" />
                <span v-else style="color: #909399; font-size: 12px; line-height: 32px;">—</span>
              </el-col>
            </el-row>
          </div>
          <el-button size="small" @click="addAssertion" style="margin-top: 8px;">
            <el-icon><Plus /></el-icon> 添加断言规则
          </el-button>
        </div>
      </el-form>

      <template #footer>
        <el-button @click="editDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="saveCase">保存用例</el-button>
        <el-button type="success" @click="saveAndExecute">保存并执行</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
/**
 * 接口测试用例管理组件
 */

import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search, Delete, Setting, Lock, Document, Box, Select } from '@element-plus/icons-vue'
import { caseApi } from '@/api/cases'
import { moduleApi } from '@/api/modules'
import { apiTestApi } from '@/api/apiTest'

// ==================== 数据 ====================

const loading = ref(false)
const loadingExec = ref(false)
const cases = ref<any[]>([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(20)
const keyword = ref('')
const filterModuleId = ref('')
const filterMethod = ref('')
const moduleTreeData = ref<any[]>([])
const recentExecutions = ref<any[]>([])

// 编辑弹窗
const editDialogVisible = ref(false)
const editMode = ref<'create' | 'edit'>('create')
const editFormRef = ref<any>(null)
const editForm = ref<any>({
  name: '',
  module_id: '',
  description: '',
  status: 'active',
  priority: 'medium',
  steps: {},
  case_type: 'api',
})

// API 请求配置（存在 steps 字段中）
const apiConfig = ref({
  method: 'GET',
  url: '',
  headers: [] as { key: string; value: string }[],
  bodyText: '',
  auth_required: true,
  sign_request: false,
  account_key: 'superadmin',
  assertions: [] as any[],
  description: '',
})

// 校验规则
const editRules = {
  name: [{ required: true, message: '请输入用例名称', trigger: 'blur' }],
  module_id: [{ required: true, message: '请选择所属模块', trigger: 'change' }],
}

// ==================== 方法 ====================

/** 获取请求方法标签类型 */
function methodTagType(method: string) {
  const map: Record<string, string> = { GET: '', POST: 'success', PUT: 'warning', DELETE: 'danger', PATCH: '' }
  return map[method] || ''
}

/** 从行数据中提取 steps 配置 */
function getSteps(row: any) {
  const steps = row.steps || {}
  if (typeof steps === 'string') {
    try { return JSON.parse(steps) } catch { return {} }
  }
  return steps
}

/** 获取断言数量 */
function getAssertionCount(row: any) {
  const steps = getSteps(row)
  return (steps.assertions || []).length
}

/** 获取模块名称 */
function getModuleName(moduleId: string) {
  const find = (nodes: any[]): string => {
    for (const n of nodes) {
      if (n.id === moduleId) return n.name
      if (n.children) {
        const r = find(n.children)
        if (r) return r
      }
    }
    return ''
  }
  return find(moduleTreeData.value) || moduleId
}

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

/** 加载模块树 */
async function loadModules() {
  try {
    const res = await moduleApi.list(0, 500)
    const items = res?.items || []
    moduleTreeData.value = items
  } catch (e) {
    console.error('加载模块失败', e)
  }
}

/** 加载用例列表 */
async function loadCases() {
  loading.value = true
  try {
    const params: any = {
      skip: (currentPage.value - 1) * pageSize.value,
      limit: pageSize.value,
    }
    if (filterModuleId.value) params.module_id = filterModuleId.value
    if (keyword.value) params.keyword = keyword.value

    // 通过 api-test 接口获取
    const res: any = await apiTestApi.listCases(params)
    cases.value = res?.items || []
    total.value = res?.total || 0
  } catch (e) {
    console.error('加载用例失败', e)
  } finally {
    loading.value = false
  }
}

/** 加载最近执行记录 */
async function loadRecentExecutions() {
  loadingExec.value = true
  try {
    const res: any = await apiTestApi.listExecutions({ skip: 0, limit: 5 })
    recentExecutions.value = res?.items || []
  } catch (e) {
    console.error('加载执行记录失败', e)
  } finally {
    loadingExec.value = false
  }
}

/** 打开新建弹窗 */
function openCreateDialog() {
  editMode.value = 'create'
  editForm.value = {
    name: '',
    module_id: '',
    description: '',
    status: 'active',
    priority: 'medium',
    steps: {},
    case_type: 'api',
  }
  apiConfig.value = {
    method: 'GET',
    url: '',
    headers: [],
    bodyText: '',
    auth_required: true,
    sign_request: false,
    account_key: 'superadmin',
    assertions: [],
    description: '',
  }
  editDialogVisible.value = true
}

/** 打开编辑弹窗 */
function openEditDialog(row: any) {
  editMode.value = 'edit'
  editForm.value = { ...row }
  const steps = getSteps(row)
  apiConfig.value = {
    method: steps.method || 'GET',
    url: steps.url || '',
    headers: Object.entries(steps.headers || {}).map(([k, v]) => ({ key: k, value: String(v) })),
    bodyText: typeof steps.body === 'object' ? JSON.stringify(steps.body, null, 2) : (steps.body || ''),
    auth_required: steps.auth_required !== false,
    sign_request: !!steps.sign_request,
    account_key: steps.account_key || 'superadmin',
    assertions: (steps.assertions || []).map((a: any) => ({ ...a })),
    description: steps.description || '',
  }
  editDialogVisible.value = true
}

/** 查看用例详情 */
function viewCaseDetail(row: any) {
  openEditDialog(row)
}

/** 合并构建 steps 配置 */
function buildSteps() {
  const headersObj: Record<string, string> = {}
  apiConfig.value.headers.forEach(h => {
    if (h.key) headersObj[h.key] = h.value
  })

  let body: any = null
  if (apiConfig.value.bodyText.trim()) {
    try {
      body = JSON.parse(apiConfig.value.bodyText)
    } catch {
      body = apiConfig.value.bodyText
    }
  }

  return {
    method: apiConfig.value.method,
    url: apiConfig.value.url,
    headers: headersObj,
    body: body,
    auth_required: apiConfig.value.auth_required,
    sign_request: apiConfig.value.sign_request,
    account_key: apiConfig.value.account_key,
    assertions: apiConfig.value.assertions.filter((a: any) => a.type && a.value !== undefined && a.value !== ''),
    description: apiConfig.value.description,
  }
}

/** 保存用例（返回新建用例的 ID，供"保存并执行"使用） */
async function saveCase(): Promise<string | null> {
  const valid = await editFormRef.value?.validate().catch(() => false)
  if (!valid) return null

  const steps = buildSteps()
  editForm.value.steps = steps

  try {
    if (editMode.value === 'create') {
      // 通过通用 cases 接口创建（指定 case_type=api）
      const payload: any = {
        name: editForm.value.name,
        module_id: editForm.value.module_id,
        status: 'active',
        case_type: 'api',
        steps: steps,
        description: apiConfig.value.description,
      }
      const res = await caseApi.create(payload)
      if (res?.id) {
        ElMessage.success('用例创建成功')
        editDialogVisible.value = false
        loadCases()
        return res.id
      }
      return null
    } else {
      // 更新用例
      const payload: any = {
        name: editForm.value.name,
        module_id: editForm.value.module_id,
        steps: steps,
        description: apiConfig.value.description,
      }
      await caseApi.update(editForm.value.id, payload)
      ElMessage.success('用例更新成功')
      editDialogVisible.value = false
      loadCases()
      return editForm.value.id
    }
  } catch (e: any) {
    ElMessage.error(e?.response?.data?.detail || '保存失败')
    return null
  }
}

/** 保存并执行 */
async function saveAndExecute() {
  const caseId = await saveCase()
  if (caseId) {
    executeSingle({ id: caseId, name: editForm.value.name })
  }
}

/** 执行单个用例 */
async function executeSingle(row: any) {
  try {
    const res = await apiTestApi.createExecution({
      case_ids: [row.id],
    })
    const executionId = (res as any)?.execution_id
    if (executionId) {
      ElMessage.success('已创建执行记录，开始执行...')
      apiTestApi.runExecution(executionId).then(() => {
        setTimeout(() => loadRecentExecutions(), 1000)
      })
    }
  } catch (e: any) {
    ElMessage.error(e?.response?.data?.detail || '执行失败')
  }
}

/** 删除用例 */
async function deleteCase(row: any) {
  try {
    await ElMessageBox.confirm(`确定删除用例「${row.name}」？`, '确认删除')
    await caseApi.delete(row.id)
    ElMessage.success('删除成功')
    loadCases()
  } catch (e: any) {
    if (e !== 'cancel') ElMessage.error(e?.response?.data?.detail || '删除失败')
  }
}

/** 添加断言 */
function addAssertion() {
  apiConfig.value.assertions.push({
    type: 'status_code',
    operator: 'equals',
    value: '200',
    path: '',
  })
}

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

onMounted(() => {
  loadModules()
  loadCases()
  loadRecentExecutions()
})
</script>

<style scoped>
.api-case-page {
  padding: 0;
}

.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
}

.header-left, .header-right {
  display: flex;
  gap: 12px;
  align-items: center;
}

.case-name {
  font-weight: 500;
  color: #303133;
  cursor: pointer;
}
.case-name:hover {
  color: #409eff;
}

.api-path {
  font-size: 12px;
  color: #606266;
  background: #f5f7fa;
  padding: 2px 6px;
  border-radius: 3px;
}

.pagination {
  margin-top: 16px;
  display: flex;
  justify-content: flex-end;
}

.exec-stats {
  font-size: 13px;
}
.exec-stats .stat {
  font-weight: 600;
}
.exec-stats .passed { color: #67c23a; }
.exec-stats .failed { color: #f56c6c; }

.variable-hint {
  font-size: 12px;
  color: #909399;
  margin: -8px 0 0 100px;
  padding-bottom: 8px;
}
.variable-hint code {
  background: #f5f7fa;
  padding: 1px 5px;
  border-radius: 3px;
  font-size: 11px;
  color: #409eff;
  margin-right: 4px;
}

.kv-editor {
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  overflow: hidden;
}
.kv-row {
  display: flex;
  border-bottom: 1px solid #f2f2f2;
  align-items: center;
}
.kv-row:last-child { border-bottom: none; }
.kv-row.header {
  background: #fafafa;
  font-size: 12px;
  color: #909399;
  padding: 6px 8px;
}
.kv-row .kv-key { flex: 1; padding: 4px; border-right: 1px solid #f2f2f2; }
.kv-row .kv-val { flex: 2; padding: 4px; border-right: 1px solid #f2f2f2; }
.kv-row .kv-act { width: 40px; display: flex; align-items: center; justify-content: center; }
.kv-add {
  padding: 6px 12px;
  font-size: 12px;
  color: #409eff;
  cursor: pointer;
  border-top: 1px solid #ebeef5;
}
.kv-add:hover { background: #f5f7fa; }

.assertion-card {
  border: 1px solid #ebeef5;
  border-radius: 6px;
  padding: 12px 12px 8px;
  margin-bottom: 8px;
  position: relative;
}
.remove-btn {
  position: absolute;
  top: 4px; right: 4px;
}

.desc {
  font-weight: 400;
  font-size: 12px;
  color: #909399;
  margin-left: 8px;
}
</style>