<!--
  组件名称：DeployExecutionDetail.vue
  组件描述：自动化部署测试 - 执行详情（历史日志回看）

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

<template>
  <div class="deploy-detail-page">
    <div class="page-header">
      <div class="header-left">
        <el-button @click="goBack" :icon="ArrowLeft" text>返回</el-button>
        <h2>执行详情</h2>
      </div>
    </div>

    <div v-if="loading" class="loading-center">
      <el-spinner />
    </div>

    <template v-else-if="execution">
      <el-card shadow="never" class="info-card">
        <el-descriptions :column="3" border>
          <el-descriptions-item label="脚本名称">{{ execution.scriptName }}</el-descriptions-item>
          <el-descriptions-item label="目标服务器">{{ execution.serverName }}</el-descriptions-item>
          <el-descriptions-item label="服务器地址">{{ execution.serverHost }}</el-descriptions-item>
          <el-descriptions-item label="远程命令">
            <el-tag type="info" size="small">{{ execution.remoteCommand }}</el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="状态">
            <el-tag :type="statusType(execution.status)" size="small">
              {{ statusLabel(execution.status) }}
            </el-tag>
          </el-descriptions-item>
          <el-descriptions-item label="退出码">
            {{ execution.exitCode !== null ? execution.exitCode : '-' }}
          </el-descriptions-item>
          <el-descriptions-item label="开始时间">{{ formatTime(execution.startTime) || '-' }}</el-descriptions-item>
          <el-descriptions-item label="结束时间">{{ formatTime(execution.endTime) || '-' }}</el-descriptions-item>
          <el-descriptions-item label="耗时">{{ execution.duration ? execution.duration + '秒' : '-' }}</el-descriptions-item>
          <el-descriptions-item label="触发者">{{ execution.triggerBy || '系统' }}</el-descriptions-item>
          <el-descriptions-item label="创建时间">{{ formatTime(execution.createdAt) }}</el-descriptions-item>
          <el-descriptions-item label="错误信息" v-if="execution.errorMessage">
            <span class="error-msg">{{ execution.errorMessage }}</span>
          </el-descriptions-item>
        </el-descriptions>
      </el-card>

      <el-card shadow="never" class="log-card">
        <template #header>
          <div class="log-header">
            <span>执行日志（共 {{ logs.length }} 条）</span>
            <div class="log-actions">
              <el-button size="small" :icon="Refresh" @click="loadLogs">刷新</el-button>
              <el-button size="small" :icon="CopyDocument" @click="copyLogs">复制日志</el-button>
            </div>
          </div>
        </template>
        <div ref="logViewerRef" class="log-viewer">
          <div v-if="logs.length === 0" class="log-empty">暂无日志记录</div>
          <div v-for="(line, idx) in logs" :key="idx" :class="['log-line', 'log-' + line.level]">
            <span class="log-time">{{ formatTime(line.timestamp) }}</span>
            <span v-if="line.level === 'INPUT'" class="log-badge">[应答]</span>
            <span v-else-if="line.level === 'OUTPUT'" class="log-badge">[输出]</span>
            <span v-else class="log-badge">[{{ line.level }}]</span>
            <span class="log-msg">{{ line.isSensitive ? '******' : line.message }}</span>
          </div>
        </div>
      </el-card>
    </template>

    <el-empty v-else description="执行记录不存在" />
  </div>
</template>

<script setup lang="ts">
/**
 * 历史执行详情：展示执行信息及全量日志
 */

import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ArrowLeft, Refresh, CopyDocument } from '@element-plus/icons-vue'
import { getExecution, listExecutionLogs } from '@/api/deploy'
import type { DeployExecution, DeployLog } from '@/types/deploy'

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

const loading = ref(false)
const execution = ref<DeployExecution | null>(null)
const logs = ref<DeployLog[]>([])
const logViewerRef = ref<HTMLElement>()

/** 加载执行详情 */
async function loadExecution() {
  const id = route.params.id as string
  if (!id) return
  loading.value = true
  try {
    execution.value = await getExecution(id)
  } catch (e: any) {
    ElMessage.error('加载执行详情失败: ' + (e.message || ''))
  } finally {
    loading.value = false
  }
}

/** 加载执行日志 */
async function loadLogs() {
  const id = route.params.id as string
  if (!id) return
  try {
    const res = await listExecutionLogs(id, { limit: 10000 })
    logs.value = res.items
    scrollToBottom()
  } catch (e: any) {
    ElMessage.error('加载日志失败: ' + (e.message || ''))
  }
}

/** 滚动到底部 */
function scrollToBottom() {
  setTimeout(() => {
    if (logViewerRef.value) {
      logViewerRef.value.scrollTop = logViewerRef.value.scrollHeight
    }
  }, 100)
}

/** 复制日志 */
function copyLogs() {
  const text = logs.value
    .map(l => `${formatTime(l.timestamp)} [${l.level}] ${l.isSensitive ? '******' : l.message}`)
    .join('\n')
  navigator.clipboard.writeText(text).then(() => {
    ElMessage.success('日志已复制到剪贴板')
  }).catch(() => {
    ElMessage.warning('复制失败，请手动选择复制')
  })
}

/** 返回上一页 */
function goBack() {
  router.push('/deploy/executions')
}

/** 状态标签类型 */
function statusType(status: string) {
  const map: Record<string, string> = { completed: 'success', running: 'primary', pending: 'info', 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 formatTime(t: string | null | undefined) {
  return t?.replace('T', ' ').substring(0, 19) || ''
}

onMounted(() => {
  loadExecution()
  loadLogs()
})
</script>

<style scoped>
.deploy-detail-page {
  padding: 0;
}

.page-header {
  margin-bottom: 16px;
}

.header-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.header-left h2 {
  font-size: 20px;
  font-weight: 600;
  color: #303133;
  margin: 0;
}

.loading-center {
  text-align: center;
  padding: 60px;
}

.info-card {
  margin-bottom: 20px;
}

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

.log-card {
  margin-bottom: 20px;
}

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

.log-actions {
  display: flex;
  gap: 8px;
}

.log-viewer {
  height: 500px;
  overflow-y: auto;
  background: #1e1e1e;
  border-radius: 4px;
  padding: 12px;
  font-family: 'Consolas', 'Courier New', monospace;
  font-size: 13px;
  line-height: 1.6;
}

.log-empty {
  color: #555;
  text-align: center;
  padding-top: 40px;
}

.log-line {
  white-space: pre-wrap;
  word-break: break-all;
}

.log-time {
  color: #888;
  margin-right: 8px;
}

.log-badge {
  margin-right: 6px;
  font-weight: 600;
}

.log-INFO .log-badge {
  color: #4fc1ff;
}

.log-OUTPUT {
  color: #d4d4d4;
}

.log-OUTPUT .log-badge {
  color: #6a9955;
}

.log-INPUT {
  color: #d7ba7d;
}

.log-INPUT .log-badge {
  color: #d7ba7d;
}

.log-ERROR {
  color: #f44747;
}

.log-ERROR .log-badge {
  color: #f44747;
}
</style>