<template>
  <div class="container">
    <div class="history-page">
      <div class="header-row">
        <h2>打包历史</h2>
        <div class="filters">
          <select v-model="filterAppName" class="filter-select">
            <option value="">全部 App</option>
            <option v-for="appName in appNames" :key="appName" :value="appName">{{ appName }}</option>
          </select>
          <select v-model="filterVersion" class="filter-select version-filter">
            <option value="">全部版本</option>
            <option v-for="version in appVersions" :key="version" :value="version">{{ version }}</option>
          </select>
          <select v-model="filterStatus" class="filter-select">
            <option value="">全部状态</option>
            <option value="pending">等待中</option>
            <option value="running">打包中</option>
            <option value="completed">已完成</option>
            <option value="failed">失败</option>
            <option value="cancelled">已取消</option>
          </select>
        </div>
      </div>
      <table class="config-table">
        <thead>
          <tr>
            <th>App 名称</th>
            <th>App 版本号</th>
            <th>打包类型</th>
            <th>Scheme</th>
            <th>状态</th>
            <th>下载地址</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="task in filteredTasks" :key="task.id">
            <td>{{ task.app_name }}</td>
            <td>{{ task.app_version }}</td>
            <td>
              <span :class="['build-type-badge', task.build_type === 'App_Store' ? 'badge-appstore' : 'badge-adhoc']">
                {{ task.build_type }}
              </span>
            </td>
            <td class="scheme-cell">{{ task.scheme_name }}</td>
            <td class="status-cell">
              <span :class="['task-status', `status-${task.status}`]">{{ statusText(task.status) }}</span>
              <span v-if="task.status === 'failed' && task.error_category" class="error-cat-badge">
                {{ errorCategoryLabel(task.error_category) }}
              </span>
            </td>
            <td class="download-cell">
              <template v-if="task.status === 'completed' && task.oss_url">
                <img v-if="task.build_type === 'Ad_Hoc' && task.qr_code_path"
                     :src="task.qr_code_path" alt="QR" class="qr-thumb"
                     @click="showQrPreview(task)">
                <a v-if="task.build_type === 'App_Store'" :href="task.oss_url" target="_blank" class="download-link">下载 IPA</a>
              </template>
              <span v-else class="text-muted">-</span>
            </td>
            <td class="action-btns">
              <button v-if="task.has_log" class="action-btn" @click="viewLogs(task.id)">日志</button>
              <button v-if="task.status === 'completed' && task.dsym_path" class="action-btn" @click="downloadDsym(task.id)">dSYM</button>
              <button v-if="task.obfuscation_maps_path" class="action-btn" @click="downloadObfMaps(task.id)">混淆映射</button>
              <button v-if="task.status !== 'running' && task.status !== 'pending'" class="action-btn btn-danger" @click="deleteTask(task.id)">删除</button>
            </td>
          </tr>
          <tr v-if="!filteredTasks.length">
            <td colspan="7" style="text-align: center; color: #999; padding: 40px;">暂无打包记录</td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- 二维码放大弹窗 -->
    <div v-if="qrPreview" class="modal-overlay" @click.self="qrPreview = null">
      <div class="qr-preview-modal">
        <div class="modal-header">
          <h3>{{ qrPreview.app_name }} - 下载二维码</h3>
          <button class="modal-close" @click="qrPreview = null">&times;</button>
        </div>
        <img :src="qrPreview.qr_code_path" alt="QR Code" class="qr-preview-img">
        <div class="qr-preview-info">
          <p><strong>App:</strong> {{ qrPreview.app_name }}</p>
          <p><strong>Scheme:</strong> {{ qrPreview.scheme_name }}</p>
          <p><strong>类型:</strong> {{ qrPreview.build_type }}</p>
        </div>
      </div>
    </div>

    <!-- 日志弹窗 -->
    <Transition name="modal">
      <div v-if="showLogModal" class="modal-overlay" @click.self="closeLogModal">
        <div class="log-modal">
          <div class="log-modal-header">
            <div class="log-modal-title">
              <svg class="log-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
              <div>
                <h3>{{ logTask?.app_name }}</h3>
                <span class="log-modal-subtitle">{{ logTask?.scheme_name }} &middot; {{ formatTime(logTask?.created_at) }}</span>
              </div>
            </div>
            <button class="modal-close" @click="closeLogModal">&times;</button>
          </div>
          <div class="log-modal-meta">
            <div class="meta-item">
              <span class="meta-label">类型</span>
              <span :class="['build-type-badge', logTask?.build_type === 'App_Store' ? 'badge-appstore' : 'badge-adhoc']">{{ logTask?.build_type }}</span>
            </div>
            <div class="meta-item">
              <span class="meta-label">分支</span>
              <span class="meta-value">{{ logTask?.branch || '-' }}</span>
            </div>
            <div class="meta-item">
              <span class="meta-label">状态</span>
              <span :class="['task-status', `status-${logTask?.status}`]">{{ statusText(logTask?.status) }}</span>
            </div>
            <div v-if="logTask?.error_category" class="meta-item">
              <span class="meta-label">原因</span>
              <span class="error-cat-badge">{{ errorCategoryLabel(logTask?.error_category) }}</span>
            </div>
            <div v-if="logTask?.obfuscation" class="meta-item">
              <span class="meta-label">混淆</span>
              <span class="meta-value">已启用</span>
            </div>
          </div>
          <div class="log-modal-actions">
            <button class="btn-toggle-logs" @click="showVerboseLogs = !showVerboseLogs">
              {{ showVerboseLogs ? '收起日志' : '展开详细日志' }}
            </button>
          </div>
          <div class="log-modal-body" ref="logContainer">
            <template v-for="(log, i) in logLines" :key="i">
              <div v-if="showVerboseLogs || log.level === 'step' || log.level === 'error' || log.level === 'warn'"
                   :class="['log-line', log.level]">
                <template v-if="log.level === 'step'">
                  <div class="step-indicator">{{ log.message }}</div>
                </template>
                <template v-else>
                  <span class="log-text">{{ log.message }}</span>
                </template>
              </div>
            </template>
            <div v-if="!logLines.length" class="log-empty">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" width="32" height="32"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
              <span>暂无日志</span>
            </div>
          </div>
          <div class="log-modal-footer">
            <span class="log-count">{{ logLines.length }} 条记录</span>
            <button class="btn-close-log" @click="closeLogModal">关闭</button>
          </div>
        </div>
      </div>
    </Transition>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, nextTick, onUnmounted, inject } from 'vue'

const getToken = inject('getToken', () => '')
const tasks = ref([])

const authFetch = (url, options = {}) => {
  const token = getToken()
  if (token) {
    options.headers = { ...options.headers, 'Authorization': `Bearer ${token}` }
  }
  return fetch(url, options)
}
const filterStatus = ref('')
const filterAppName = ref('')
const filterVersion = ref('')
const showLogModal = ref(false)
const logTask = ref(null)
const logLines = ref([])
const logContainer = ref(null)
const showVerboseLogs = ref(false)
const qrPreview = ref(null)
let logWs = null

const getAppVersion = (task) => {
  try {
    return JSON.parse(task.config_json || '{}').VERSION || '-'
  } catch {
    return '-'
  }
}

const appNames = computed(() => [...new Set(tasks.value.map(task => task.app_name).filter(Boolean))].sort())
const appVersions = computed(() => [...new Set(
  tasks.value.map(task => task.app_version).filter(version => version && version !== '-')
)].sort((a, b) => b.localeCompare(a, undefined, { numeric: true })))

const showQrPreview = (task) => {
  qrPreview.value = task
}

const filteredTasks = computed(() => {
  return tasks.value.filter(task => {
    // 默认隐藏已取消的任务
    if (!filterStatus.value && task.status === 'cancelled') return false
    if (filterAppName.value && task.app_name !== filterAppName.value) return false
    if (filterVersion.value && task.app_version !== filterVersion.value) return false
    if (filterStatus.value && task.status !== filterStatus.value) return false
    return true
  })
})

onMounted(async () => {
  const res = await authFetch('/api/tasks?limit=100')
  const data = await res.json()
  tasks.value = data.map(task => ({ ...task, app_version: getAppVersion(task) }))
})

const viewLogs = async (taskId) => {
  const task = tasks.value.find(t => t.id === taskId)
  if (!task) return
  logTask.value = task
  logLines.value = []
  showVerboseLogs.value = false
  showLogModal.value = true

  if (task.status === 'running' || task.status === 'pending') {
    // 进行中的任务，连接 WebSocket 获取实时日志
    connectLogWs(taskId)
  } else {
    // 已完成/失败的任务，先从 REST API 获取完整日志
    try {
      const res = await authFetch(`/api/tasks/${taskId}/log`)
      if (res.ok) {
        const data = await res.json()
        if (data.log) {
          const lines = data.log.split('\n').filter(Boolean)
          logLines.value = lines.map(line => {
            const m = line.match(/^\[(.+?)\]\s+\[(.+?)\]\s+(.*)$/)
            if (m) {
              return { timestamp: m[1], level: m[2].toLowerCase(), message: m[3] }
            }
            return { timestamp: '', level: 'info', message: line }
          })
        }
      }
    } catch {}
    // 日志为空时，回退到元数据展示
    if (!logLines.value.length) {
      if (task.error_message) {
        logLines.value.push({ level: 'error', message: `错误: ${task.error_message}` })
      }
      if (task.error_category) {
        logLines.value.push({ level: 'warn', message: `分类: ${errorCategoryLabel(task.error_category)}` })
      }
      if (task.config_json) {
        try {
          const cfg = JSON.parse(task.config_json)
          logLines.value.push({ level: 'info', message: `版本: ${cfg.VERSION}  Build: ${cfg.BUILD_VERSION}` })
          logLines.value.push({ level: 'info', message: `Scheme: ${cfg.SCHEME}  类型: ${cfg.BUILD_TYPE}` })
          if (cfg.CERTIFICATE) logLines.value.push({ level: 'info', message: `证书: ${cfg.CERTIFICATE}` })
          if (cfg.BUNDLE_ID) logLines.value.push({ level: 'info', message: `BundleID: ${cfg.BUNDLE_ID}` })
        } catch {}
      }
      if (task.status === 'completed') {
        logLines.value.push({ level: 'step', message: '[打包完成]' })
        if (task.oss_url) logLines.value.push({ level: 'info', message: `下载链接: ${task.oss_url}` })
      } else if (task.status === 'failed') {
        logLines.value.push({ level: 'step', message: '[打包失败]' })
      } else if (task.status === 'cancelled') {
        logLines.value.push({ level: 'info', message: '任务已取消' })
      }
    }
  }
}

const connectLogWs = (taskId) => {
  if (logWs) { logWs.close(); logWs = null }
  const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
  const token = getToken()
  if (!token) return
  const ws = new WebSocket(`${protocol}//${location.host}/ws/tasks/${taskId}`, [`jwt.${token}`])
  logWs = ws
  ws.onmessage = (event) => {
    const msg = JSON.parse(event.data)
    if (msg.level !== 'heartbeat') {
      logLines.value.push(msg)
      nextTick(() => {
        if (logContainer.value) logContainer.value.scrollTop = logContainer.value.scrollHeight
      })
    }
  }
  ws.onclose = () => { logWs = null }
}

const closeLogModal = () => {
  if (logWs) { logWs.close(); logWs = null }
  showLogModal.value = false
  logTask.value = null
  logLines.value = []
}

onUnmounted(() => {
  if (logWs) { logWs.close(); logWs = null }
})

const downloadDsym = (taskId) => {
  window.open(`/api/tasks/${taskId}/dsym`)
}

const downloadObfMaps = (taskId) => {
  window.open(`/api/tasks/${taskId}/obfuscation-maps`)
}

const deleteTask = async (taskId) => {
  if (!confirm('确定要删除这条打包记录及其远端分发文件吗？')) return
  try {
    const res = await authFetch(`/api/tasks/${taskId}/delete`, { method: 'DELETE' })
    if (res.ok) {
      tasks.value = tasks.value.filter(t => t.id !== taskId)
    } else {
      const data = await res.json().catch(() => ({}))
      alert(data.detail || '删除失败，请稍后重试')
    }
  } catch {
    alert('删除失败，请检查网络后重试')
  }
}

const formatTime = (t) => {
  if (!t) return '-'
  const d = t.endsWith('Z') || t.includes('+') ? new Date(t) : new Date(t + 'Z')
  return `${d.getMonth() + 1}/${d.getDate()}`
}

const formatDuration = (started, completed) => {
  if (!started || !completed) return ''
  const start = started.endsWith('Z') || started.includes('+') ? new Date(started) : new Date(started + 'Z')
  const end = completed.endsWith('Z') || completed.includes('+') ? new Date(completed) : new Date(completed + 'Z')
  const seconds = Math.floor((end - start) / 1000)
  if (seconds < 0) return ''
  if (seconds < 60) return `${seconds}秒`
  const minutes = Math.floor(seconds / 60)
  const remainSeconds = seconds % 60
  if (minutes < 60) return `${minutes}分${remainSeconds}秒`
  const hours = Math.floor(minutes / 60)
  const remainMinutes = minutes % 60
  return `${hours}时${remainMinutes}分`
}

const statusText = (s) => {
  const map = { pending: '等待中', running: '打包中', completed: '已完成', failed: '失败', cancelled: '已取消' }
  return map[s] || s
}

const errorCategoryLabel = (cat) => {
  const map = {
    certificate: '证书问题',
    provisioning: '描述文件',
    compilation: '编译错误',
    dependency: '依赖问题',
    git: '代码拉取',
    config: '配置问题',
    build: '构建配置',
    timeout: '打包超时',
    upload: '上传失败',
    unknown: '未知错误',
  }
  return map[cat] || '未知错误'
}
</script>

<style scoped>
.container { max-width: 1400px; margin: 0 auto; padding: 24px; }
.history-page { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.header-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.header-row h2 { font-size: 18px; color: #1a1a2e; margin: 0; }
.filters { display: flex; gap: 12px; }
.filter-select { padding: 8px 12px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 14px; min-width: 120px; }
.filter-select:focus { outline: none; border-color: #1890ff; }

.config-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.config-table th, .config-table td { padding: 12px; text-align: left; border-bottom: 1px solid #f0f0f0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.config-table th { background: #fafafa; font-weight: 500; color: #666; font-size: 13px; }
.config-table td { font-size: 14px; }
.config-table th:nth-child(1) { width: 14%; }
.config-table th:nth-child(2) { width: 12%; }
.config-table th:nth-child(3) { width: 10%; }
.config-table th:nth-child(4) { width: 18%; }
.config-table th:nth-child(5) { width: 15%; }
.config-table th:nth-child(6) { width: 12%; }
.config-table th:nth-child(7) { width: 19%; }
.config-table tr:hover { background: #fafafa; }

.build-type-badge { padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; }
.badge-adhoc { background: #e6f7ff; color: #1890ff; }
.badge-appstore { background: #f6ffed; color: #52c41a; }

.action-btns { white-space: normal; }
.action-btns .action-btn { margin-right: 6px; margin-bottom: 4px; display: inline-block; vertical-align: middle; }
.download-cell { white-space: normal; }
.scheme-cell { white-space: normal !important; overflow: visible !important; text-overflow: clip !important; overflow-wrap: anywhere; }
.action-btn { padding: 4px 12px; border: 1px solid #d9d9d9; border-radius: 4px; background: white; cursor: pointer; font-size: 12px; }
.action-btn:hover { border-color: #1890ff; color: #1890ff; }

.task-status { padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 500; }
.status-cell { white-space: normal !important; overflow: visible !important; text-overflow: clip !important; overflow-wrap: anywhere; }
.status-cell .task-status, .status-cell .error-cat-badge { display: inline-block; margin-bottom: 4px; }
.status-pending { background: #f0f0f0; color: #666; }
.status-running { background: #e6f7ff; color: #1890ff; }
.status-completed { background: #f6ffed; color: #52c41a; }
.status-failed { background: #fff2f0; color: #ff4d4f; }
.status-cancelled { background: #f0f0f0; color: #999; }

.error-cat-badge {
  display: inline-block; margin-left: 6px; padding: 2px 8px;
  border-radius: 4px; font-size: 11px; font-weight: 500;
  background: #fff2f0; color: #ff4d4f; border: 1px solid #ffccc7;
}

.modal-overlay {
  position: fixed; top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center; z-index: 1000;
}

/* 弹窗过渡动画 */
.modal-enter-active { transition: all 0.25s ease-out; }
.modal-leave-active { transition: all 0.2s ease-in; }
.modal-enter-from, .modal-leave-to { opacity: 0; }
.modal-enter-from .log-modal, .modal-leave-to .log-modal { transform: translateY(20px) scale(0.97); }

.log-modal {
  background: white; border-radius: 16px; width: 740px; max-height: 82vh;
  display: flex; flex-direction: column; overflow: hidden;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.05);
  transition: transform 0.25s ease-out;
}

/* 头部 */
.log-modal-header {
  display: flex; justify-content: space-between; align-items: flex-start;
  padding: 20px 24px 16px; border-bottom: 1px solid #f0f0f0;
}
.log-modal-title { display: flex; align-items: center; gap: 12px; }
.log-icon { width: 36px; height: 36px; color: #1890ff; background: #e6f7ff; border-radius: 10px; padding: 7px; flex-shrink: 0; }
.log-modal-title h3 { font-size: 17px; margin: 0; color: #1a1a2e; font-weight: 600; }
.log-modal-subtitle { font-size: 12px; color: #999; margin-top: 2px; display: block; }

/* 元信息栏 */
.log-modal-meta {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 12px 24px; background: #fafbfc; border-bottom: 1px solid #f0f0f0;
}
.meta-item { display: flex; align-items: center; gap: 6px; }
.meta-label { font-size: 12px; color: #999; }
.meta-value { font-size: 13px; color: #333; font-weight: 500; }

.version-filter { width: 150px; }

/* 日志操作栏 */
.log-modal-actions {
  padding: 8px 20px; background: #161b22; border-bottom: 1px solid #30363d;
  display: flex; justify-content: flex-end;
}
.btn-toggle-logs {
  background: transparent; border: 1px solid #30363d; color: #58a6ff;
  padding: 4px 12px; border-radius: 6px; font-size: 12px; cursor: pointer;
  transition: all 0.2s;
}
.btn-toggle-logs:hover { background: #1f2937; border-color: #58a6ff; }

/* 日志内容区 */
.log-modal-body {
  flex: 1; min-height: 0; max-height: 56vh; overflow-y: auto;
  padding: 16px 20px; background: #0d1117;
  font-family: 'SF Mono', 'Monaco', 'Menlo', 'Consolas', monospace;
  font-size: 12.5px; line-height: 1.7;
}
.log-line { margin-bottom: 2px; padding: 1px 0; }
.log-line.info { color: #8b949e; }
.log-line.info .log-text { color: #8b949e; }
.log-line.warn { color: #d29922; }
.log-line.error { color: #f85149; }
.log-line.step { margin: 8px 0 4px; }
.step-indicator {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(56, 139, 253, 0.1); border: 1px solid rgba(56, 139, 253, 0.2);
  padding: 5px 14px; border-radius: 6px;
  font-weight: 600; color: #58a6ff; font-size: 13px;
}
.log-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 48px 0; color: #484f58;
}

/* 底部 */
.log-modal-footer {
  display: flex; justify-content: space-between; align-items: center;
  padding: 12px 24px; border-top: 1px solid #f0f0f0; background: #fafbfc;
}
.log-count { font-size: 12px; color: #999; }
.btn-close-log {
  padding: 7px 20px; border: 1px solid #d9d9d9; border-radius: 6px;
  background: white; color: #333; font-size: 13px; cursor: pointer;
  transition: all 0.2s;
}
.btn-close-log:hover { border-color: #1890ff; color: #1890ff; }
.download-link { color: #1890ff; text-decoration: none; font-size: 13px; }
.download-link:hover { text-decoration: underline; }
.text-muted { color: #999; font-size: 13px; }
.qr-thumb { width: 40px; height: 40px; cursor: pointer; border-radius: 4px; border: 1px solid #e8e8e8; }
.qr-thumb:hover { border-color: #1890ff; }
.btn-danger { color: #ff4d4f; border-color: #ff4d4f; }
.btn-danger:hover { background: #ff4d4f; color: white; }

/* 二维码放大弹窗 */
.qr-preview-modal { background: white; border-radius: 12px; padding: 24px; width: 380px; text-align: center; position: relative; }
.qr-preview-modal .modal-header { display: flex; justify-content: center; align-items: center; position: relative; margin-bottom: 16px; }
.qr-preview-modal .modal-close { position: absolute; right: 0; top: 50%; transform: translateY(-50%); background: none; border: none; font-size: 20px; cursor: pointer; color: #999; }
.qr-preview-modal .modal-close:hover { color: #333; }
.qr-preview-img { width: 260px; height: 260px; border: 1px solid #f0f0f0; border-radius: 8px; margin: 16px 0; }
.qr-preview-info { text-align: left; padding: 12px 16px; background: #fafafa; border-radius: 8px; }
.qr-preview-info p { margin: 6px 0; font-size: 13px; color: #555; }
</style>
