<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue'
import type { UploadFile, UploadFiles } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Refresh, UploadFilled } from '@element-plus/icons-vue'
import http, { apiErrorMessage } from '../api/http'

type UpdatePlatform = 'Android' | 'Ios'
type UpdateChannel = 'Production' | 'Staging'
type UpdateStatus = 'Draft' | 'Published' | 'Archived'

interface AppUpdateRelease {
  id: string
  platform: UpdatePlatform
  channel: UpdateChannel
  version: string
  nativeVersion: string
  status: UpdateStatus
  releaseNotes?: string
  fileName: string
  fileSize: number
  sha256: string
  createdByUserName: string
  createdAt: string
  publishedByUserName?: string
  publishedAt?: string
}

interface PageResult<T> {
  items: T[]
  total: number
  page: number
  pageSize: number
}

const loading = ref(false)
const uploadBusy = ref(false)
const rows = ref<AppUpdateRelease[]>([])
const total = ref(0)
const uploadVisible = ref(false)
const selectedBundle = ref<File>()
const uploadFiles = ref<UploadFiles>([])
const filter = reactive({
  page: 1,
  pageSize: 20,
  platform: '',
  channel: '',
})
const form = reactive({
  platform: 'Android' as UpdatePlatform,
  channel: 'Production' as UpdateChannel,
  version: '',
  nativeVersion: '1.0',
  releaseNotes: '',
})

function formatTime(value?: string) {
  if (!value) return '—'
  return new Date(value).toLocaleString('zh-CN', { hour12: false })
}

function formatBytes(value: number) {
  if (value < 1024) return `${value} B`
  if (value < 1024 ** 2) return `${(value / 1024).toFixed(1)} KB`
  return `${(value / 1024 ** 2).toFixed(1)} MB`
}

function platformLabel(value: UpdatePlatform) {
  return value === 'Android' ? 'Android' : 'iOS'
}

function channelLabel(value: UpdateChannel) {
  return value === 'Production' ? '正式' : '测试'
}

function statusLabel(value: UpdateStatus) {
  return value === 'Published'
    ? '已发布'
    : value === 'Archived'
      ? '已归档'
      : '草稿'
}

function statusType(value: UpdateStatus) {
  return value === 'Published'
    ? 'success'
    : value === 'Archived'
      ? 'info'
      : 'warning'
}

async function loadReleases() {
  loading.value = true
  try {
    const params: Record<string, string | number> = {
      page: filter.page,
      pageSize: filter.pageSize,
    }
    if (filter.platform) params.platform = filter.platform
    if (filter.channel) params.channel = filter.channel
    const { data } = await http.get<PageResult<AppUpdateRelease>>(
      '/app-updates/releases',
      { params },
    )
    rows.value = data.items
    total.value = data.total
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    loading.value = false
  }
}

function search() {
  filter.page = 1
  loadReleases()
}

function openUpload() {
  Object.assign(form, {
    platform: 'Android',
    channel: 'Production',
    version: '',
    nativeVersion: '1.0',
    releaseNotes: '',
  })
  selectedBundle.value = undefined
  uploadFiles.value = []
  uploadVisible.value = true
}

function handleBundleChange(file: UploadFile, files: UploadFiles) {
  uploadFiles.value = files.slice(-1)
  selectedBundle.value = file.raw
}

function handleBundleRemove() {
  selectedBundle.value = undefined
  uploadFiles.value = []
}

async function uploadRelease() {
  if (!selectedBundle.value) {
    ElMessage.warning('请选择更新 ZIP。')
    return
  }
  if (!form.version.trim() || !form.nativeVersion.trim()) {
    ElMessage.warning('请填写热更新版本和兼容的原生版本。')
    return
  }

  const body = new FormData()
  body.append('bundle', selectedBundle.value)
  body.append('platform', form.platform)
  body.append('channel', form.channel)
  body.append('version', form.version.trim())
  body.append('nativeVersion', form.nativeVersion.trim())
  body.append('releaseNotes', form.releaseNotes.trim())

  uploadBusy.value = true
  try {
    await http.post('/app-updates/releases', body, {
      timeout: 10 * 60 * 1000,
    })
    ElMessage.success('更新包已上传为草稿，确认无误后再发布。')
    uploadVisible.value = false
    await loadReleases()
  } catch (error) {
    ElMessage.error(apiErrorMessage(error))
  } finally {
    uploadBusy.value = false
  }
}

async function publishRelease(tableRow: Record<string, unknown>) {
  const row = tableRow as unknown as AppUpdateRelease
  const isRollback = row.status === 'Archived'
  try {
    await ElMessageBox.confirm(
      isRollback
        ? `将把 ${platformLabel(row.platform)} ${row.nativeVersion} 的正式通道回滚到 ${row.version}。新启动的 App 将下载此版本。`
        : `确认发布 ${row.version}？同平台、通道和原生版本下当前生效的更新将自动归档。`,
      isRollback ? '确认版本回滚' : '确认发布更新',
      {
        type: isRollback ? 'warning' : 'info',
        confirmButtonText: isRollback ? '确认回滚' : '发布',
        cancelButtonText: '取消',
      },
    )
    await http.post(`/app-updates/releases/${row.id}/publish`)
    ElMessage.success(isRollback ? '已回滚到指定版本。' : '更新已发布。')
    await loadReleases()
  } catch (error) {
    if (error === 'cancel' || error === 'close') return
    ElMessage.error(apiErrorMessage(error))
  }
}

async function deleteRelease(tableRow: Record<string, unknown>) {
  const row = tableRow as unknown as AppUpdateRelease
  try {
    await ElMessageBox.confirm(
      `将永久删除更新包 ${row.fileName}。此操作不会影响已安装到设备的版本。`,
      '删除更新包',
      {
        type: 'warning',
        confirmButtonText: '删除',
        cancelButtonText: '取消',
      },
    )
    await http.delete(`/app-updates/releases/${row.id}`)
    ElMessage.success('更新包已删除。')
    await loadReleases()
  } catch (error) {
    if (error === 'cancel' || error === 'close') return
    ElMessage.error(apiErrorMessage(error))
  }
}

onMounted(loadReleases)
</script>

<template>
  <section class="update-panel">
    <header class="update-heading">
      <div>
        <span>SELF-HOSTED OTA</span>
        <h3>App 前端热更新</h3>
        <p>上传构建后的 ZIP，按原生版本和通道发布；保留历史包用于快速回滚。</p>
      </div>
      <div class="heading-actions">
        <el-button :icon="Refresh" :loading="loading" @click="loadReleases">
          刷新
        </el-button>
        <el-button type="primary" :icon="UploadFilled" @click="openUpload">
          上传更新包
        </el-button>
      </div>
    </header>

    <div class="update-rules">
      <strong>发布边界</strong>
      <span>仅限 HTML、JavaScript、CSS 和静态资源</span>
      <span>新增原生插件、权限或 Android/iOS 代码必须重新发版</span>
      <span>更新失败时 App 自动回退到上一个可用版本</span>
    </div>

    <div class="update-filter">
      <el-select v-model="filter.platform" clearable placeholder="全部平台">
        <el-option label="Android" value="Android" />
        <el-option label="iOS" value="Ios" />
      </el-select>
      <el-select v-model="filter.channel" clearable placeholder="全部通道">
        <el-option label="正式通道" value="Production" />
        <el-option label="测试通道" value="Staging" />
      </el-select>
      <el-button type="primary" @click="search">查询</el-button>
    </div>

    <el-table v-loading="loading" :data="rows" stripe>
      <el-table-column label="版本" min-width="142">
        <template #default="{ row }">
          <strong class="version">{{ row.version }}</strong>
          <small>原生 {{ row.nativeVersion }}</small>
        </template>
      </el-table-column>
      <el-table-column label="目标" width="132">
        <template #default="{ row }">
          {{ platformLabel(row.platform) }} · {{ channelLabel(row.channel) }}
        </template>
      </el-table-column>
      <el-table-column label="状态" width="94">
        <template #default="{ row }">
          <el-tag :type="statusType(row.status)" effect="plain">
            {{ statusLabel(row.status) }}
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column label="更新说明" min-width="210" show-overflow-tooltip>
        <template #default="{ row }">{{ row.releaseNotes || '—' }}</template>
      </el-table-column>
      <el-table-column label="更新包" min-width="205" show-overflow-tooltip>
        <template #default="{ row }">
          <span class="file-name">{{ row.fileName }}</span>
          <small>{{ formatBytes(row.fileSize) }} · {{ row.sha256.slice(0, 12) }}…</small>
        </template>
      </el-table-column>
      <el-table-column label="时间" width="168">
        <template #default="{ row }">
          {{ formatTime(row.publishedAt || row.createdAt) }}
        </template>
      </el-table-column>
      <el-table-column label="操作" fixed="right" width="152">
        <template #default="{ row }">
          <el-button
            v-if="row.status !== 'Published'"
            link
            type="primary"
            @click="publishRelease(row)"
          >
            {{ row.status === 'Archived' ? '回滚至此版本' : '发布' }}
          </el-button>
          <el-button
            v-if="row.status !== 'Published'"
            link
            type="danger"
            @click="deleteRelease(row)"
          >
            删除
          </el-button>
          <span v-else class="active-label">当前生效</span>
        </template>
      </el-table-column>
      <template #empty>
        <span>尚未上传 App 热更新包。</span>
      </template>
    </el-table>

    <el-pagination
      v-model:current-page="filter.page"
      v-model:page-size="filter.pageSize"
      background
      layout="total, prev, pager, next"
      :total="total"
      @current-change="loadReleases"
    />

    <el-dialog
      v-model="uploadVisible"
      title="上传 App 热更新包"
      width="min(620px, 92vw)"
      destroy-on-close
    >
      <el-form label-position="top">
        <div class="form-grid">
          <el-form-item label="平台">
            <el-select v-model="form.platform">
              <el-option label="Android" value="Android" />
              <el-option label="iOS" value="Ios" />
            </el-select>
          </el-form-item>
          <el-form-item label="通道">
            <el-select v-model="form.channel">
              <el-option label="正式通道" value="Production" />
              <el-option label="测试通道" value="Staging" />
            </el-select>
          </el-form-item>
          <el-form-item label="热更新版本">
            <el-input v-model="form.version" placeholder="例如 1.0.1" />
          </el-form-item>
          <el-form-item label="兼容的原生版本">
            <el-input v-model="form.nativeVersion" placeholder="当前 Android 为 1.0" />
          </el-form-item>
        </div>
        <el-form-item label="更新说明">
          <el-input
            v-model="form.releaseNotes"
            type="textarea"
            :rows="3"
            maxlength="1000"
            show-word-limit
            placeholder="说明本次修复内容，不要包含敏感信息。"
          />
        </el-form-item>
        <el-form-item label="更新 ZIP">
          <el-upload
            v-model:file-list="uploadFiles"
            accept=".zip,application/zip"
            :auto-upload="false"
            :limit="1"
            :on-change="handleBundleChange"
            :on-remove="handleBundleRemove"
          >
            <el-button :icon="UploadFilled">选择 ZIP</el-button>
            <template #tip>
              <div class="el-upload__tip">
                最大 30 MB，ZIP 根目录必须直接包含 index.html。
              </div>
            </template>
          </el-upload>
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="uploadVisible = false">取消</el-button>
        <el-button type="primary" :loading="uploadBusy" @click="uploadRelease">
          上传为草稿
        </el-button>
      </template>
    </el-dialog>
  </section>
</template>

<style scoped>
.update-panel {
  background: #fff;
  border: 1px solid #d8dee1;
  min-width: 0;
}

.update-heading {
  align-items: center;
  border-bottom: 1px solid #d8dee1;
  display: flex;
  justify-content: space-between;
  padding: 16px 18px;
}

.update-heading span {
  color: #74818a;
  font-family: "Cascadia Mono", Consolas, monospace;
  font-size: 10px;
  letter-spacing: .09em;
}

.update-heading h3 { font-size: 17px; margin: 3px 0 4px; }
.update-heading p { color: #66747e; font-size: 12px; margin: 0; }
.heading-actions { display: flex; gap: 8px; }

.update-rules {
  align-items: center;
  background: #f5f8f7;
  border-bottom: 1px solid #e0e6e5;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  padding: 10px 18px;
}

.update-rules strong { color: #1f7468; font-size: 11px; }
.update-rules span { color: #66747e; font-size: 11px; }
.update-rules span::before { content: "·"; margin-right: 8px; }

.update-filter {
  display: flex;
  gap: 9px;
  padding: 14px 18px 0;
}

.update-filter .el-select { width: 160px; }
.version,
.file-name { display: block; font-family: "Cascadia Mono", Consolas, monospace; }
.version { color: #1f7468; font-size: 13px; }
.file-name { font-size: 11px; }
small { color: #74818a; display: block; font-size: 10px; margin-top: 4px; }
.active-label { color: #1f7468; font-size: 11px; font-weight: 700; }
.el-table { margin-top: 12px; }
.el-pagination { justify-content: flex-end; padding: 16px 18px; }
.form-grid { display: grid; gap: 0 14px; grid-template-columns: 1fr 1fr; }
.form-grid .el-select { width: 100%; }

@media (max-width: 720px) {
  .update-heading { align-items: flex-start; display: block; }
  .heading-actions { margin-top: 12px; }
  .update-filter { flex-wrap: wrap; }
  .update-filter .el-select { flex: 1 1 140px; }
  .form-grid { display: block; }
}
</style>
