<template>
  <v-container fluid class="pa-6">
    <!-- ZIP Upload -->
    <v-card elevation="0" border rounded="lg" class="mb-4">
      <v-card-title class="text-subtitle-1">源文件</v-card-title>
      <v-card-text>
        <div v-if="!uploadedZip">
          <div
            class="border-dashed border rounded pa-6 text-center cursor-pointer"
            :class="{ 'border-primary bg-primary-lighten-5': isDragging }"
            @dragover.prevent="isDragging = true"
            @dragleave="isDragging = false"
            @drop.prevent="handleZipDrop"
            @click="triggerZipInput"
          >
            <v-icon size="48" color="grey">mdi-cloud-upload-outline</v-icon>
            <div class="text-body-2 mt-2">拖拽 ZIP 文件到此处，或点击选择</div>
            <div class="text-caption text-medium-emphasis">支持 .zip 格式</div>
          </div>
          <input ref="zipInput" type="file" accept=".zip" hidden @change="handleZipSelect" />
        </div>
        <div v-else class="d-flex align-center">
          <v-icon class="mr-2">mdi-folder-zip</v-icon>
          <span>{{ uploadedZip.name }} ({{ uploadedZip.fileCount }} 个文件, {{ formatSize(uploadedZip.size) }})</span>
          <v-spacer />
          <v-btn size="small" variant="text" color="error" @click="clearZipUpload">清除</v-btn>
        </div>
        <v-progress-linear v-if="zipUploading" indeterminate class="mt-2" />
      </v-card-text>
    </v-card>

    <!-- Toolbar -->
    <v-card elevation="0" border rounded="lg" class="mb-4 pa-4">
      <v-row align="center" dense>
        <v-col cols="12" sm="4">
          <v-text-field v-model="sourcePath" label="推送源路径" variant="outlined" density="compact" prepend-inner-icon="mdi-folder" />
        </v-col>
        <v-col cols="12" sm="4">
          <v-text-field v-model="targetPath" label="目标路径" variant="outlined" density="compact" prepend-inner-icon="mdi-folder-marker" />
        </v-col>
        <v-col cols="12" sm="4" class="d-flex ga-2 justify-end">
          <v-btn size="small" variant="tonal" prepend-icon="mdi-eye" @click="doPreview">预览</v-btn>
          <v-btn size="small" color="primary" variant="flat" prepend-icon="mdi-upload" @click="doPush" :loading="pushing">推送</v-btn>
        </v-col>
      </v-row>
    </v-card>

    <!-- Server Selection -->
    <v-card elevation="0" border rounded="lg" class="mb-4">
      <v-card-title class="d-flex align-center">
        <v-checkbox v-model="selectAll" label="全选" density="compact" hide-details class="mr-4" @update:model-value="toggleAll" />
        <v-text-field
          v-model="serverSearch"
          prepend-inner-icon="mdi-magnify"
          label="搜索服务器..."
          density="compact"
          hide-details
          variant="outlined"
          rounded
          style="max-width: 240px"
        />
      </v-card-title>
      <v-divider />
      <v-card-text class="pa-2">
        <template v-if="filteredServers.length === 0">
          <div class="text-center text-medium-emphasis py-6">暂无服务器</div>
        </template>
        <template v-else>
          <div v-for="group in serversByCategory" :key="group.category" class="mb-4">
            <div class="d-flex align-center px-2 mb-2">
              <v-btn
                size="x-small"
                variant="text"
                :color="isCategoryAllSelected(group.category) ? 'primary' : 'default'"
                @click="toggleCategory(group.category)"
              >
                <v-icon size="16" class="mr-1">
                  {{ isCategoryAllSelected(group.category) ? 'mdi-checkbox-marked' : 'mdi-checkbox-blank-outline' }}
                </v-icon>
                {{ group.category }}
              </v-btn>
              <span class="text-caption text-medium-emphasis ml-1">
                ({{ group.servers.filter(s => selectedIds.has(s.id)).length }}/{{ group.servers.length }})
              </span>
            </div>
            <v-row dense>
              <v-col cols="6" sm="4" md="3" lg="2" v-for="s in group.servers" :key="s.id">
                <v-card
                  :color="selectedIds.has(s.id) ? 'primary' : undefined"
                  :variant="selectedIds.has(s.id) ? 'tonal' : 'outlined'"
                  rounded="lg"
                  class="pa-3 cursor-pointer"
                  @click="toggleServer(s.id)"
                >
                  <div class="d-flex align-center ga-2">
                    <v-icon :color="s.status === 'online' ? 'success' : 'error'" size="12">mdi-circle</v-icon>
                    <span class="text-body-2 font-weight-medium text-truncate">{{ s.name }}</span>
                  </div>
                  <div class="text-caption text-medium-emphasis text-truncate">{{ s.domain }}</div>
                  <div v-if="pushStatus[s.id]" class="text-caption mt-1">
                    <v-chip :color="pushColor(pushStatus[s.id])" size="x-small" variant="tonal" label border="sm">{{ pushStatus[s.id] }}</v-chip>
                  </div>
                </v-card>
              </v-col>
            </v-row>
          </div>
        </template>
      </v-card-text>
    </v-card>

    <!-- Sync Mode -->
    <v-card elevation="0" border rounded="lg" class="mb-4">
      <v-card-title class="text-subtitle-1">同步模式</v-card-title>
      <v-card-text>
        <v-radio-group v-model="syncMode" inline density="compact">
          <v-radio label="增量同步" value="incremental" />
          <v-radio label="全量同步" value="full" />
          <v-radio label="校验和同步" value="checksum" />
        </v-radio-group>
        <v-alert v-if="syncMode === 'full'" type="warning" density="compact" variant="tonal" class="mt-2">
          全量同步会删除目标目录中不存在于源目录的文件
        </v-alert>
        <div class="text-caption text-medium-emphasis mt-1">
          {{ syncModeDesc }}
        </div>
      </v-card-text>
    </v-card>

    <!-- Push Progress -->
    <v-card v-if="pushing" elevation="0" border rounded="lg" class="mb-4 pa-4">
      <div class="text-subtitle-2 mb-2">推送进度</div>
      <v-progress-linear :model-value="pushProgress" color="primary" height="8" rounded />
      <div class="text-caption text-medium-emphasis mt-1">{{ completedCount }} / {{ selectedIds.size }} 台完成</div>
    </v-card>

    <!-- WebSocket Per-Server Progress -->
    <v-card v-if="wsProgressItems.length > 0" class="mb-4" elevation="0" border rounded="lg">
      <v-card-title class="d-flex align-center text-subtitle-1">
        推送进度
        <v-spacer />
        <v-btn
          v-if="pushBatchId && wsProgressItems.some(s => s.status === 'running' || s.status === 'pending')"
          size="small"
          variant="tonal"
          color="error"
          class="mr-2"
          :loading="cancelling"
          @click="cancelPush"
        >
          取消推送
        </v-btn>
        <v-chip size="small" variant="tonal" color="primary">
          {{ wsProgressItems.filter(s => s.status === 'success').length }}/{{ wsProgressItems.length }} 完成
        </v-chip>
      </v-card-title>
      <v-divider />
      <v-card-text>
        <v-list density="compact">
          <v-list-item v-for="s in wsProgressItems" :key="s.id" :title="s.name">
            <template #append>
              <v-icon v-if="s.status === 'pending'" color="grey">mdi-clock-outline</v-icon>
              <v-icon v-else-if="s.status === 'running'" color="blue">mdi-loading</v-icon>
              <v-icon v-else-if="s.status === 'success'" color="green">mdi-check-circle</v-icon>
              <v-icon v-else color="red">mdi-alert-circle</v-icon>
            </template>
            <template v-if="s.detail" #subtitle>
              <span :class="s.status === 'failed' ? 'text-error' : 'text-medium-emphasis'">{{ s.detail }}</span>
            </template>
          </v-list-item>
        </v-list>
      </v-card-text>
    </v-card>

    <!-- Preview Dialog -->
    <v-dialog v-model="showPreview" max-width="600">
      <v-card border>
        <v-card-title>推送预览</v-card-title>
        <v-card-text>
          <v-alert v-if="previewData.error" type="error" class="mb-2">{{ previewData.error }}</v-alert>
          <div v-else>
            <div class="mb-2">将推送到 <strong>{{ selectedIds.size }}</strong> 台服务器</div>
            <div v-if="previewData.files" class="text-body-2">{{ previewData.files }} 个文件，{{ previewData.size }}</div>
          </div>
        </v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="showPreview = false">关闭</v-btn>
          <v-btn color="primary" variant="flat" @click="showPreview = false; doPush()">确认推送</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>

    <!-- Push Log -->
    <v-card elevation="0" border rounded="lg">
      <v-card-title>推送历史</v-card-title>
      <v-data-table-server
        :items="logs"
        :headers="logHeaders"
        :items-length="logTotal"
        :loading="logLoading"
        :page="logPage"
        :items-per-page="15"
        hover
        density="comfortable"
        @update:page="logPage = $event; loadLogs()"
      >
        <template #item.status="{ item }">
          <v-chip :color="item.status === 'success' ? 'success' : item.status === 'failed' ? 'error' : 'warning'" size="x-small" variant="tonal" label border="sm">
            {{ item.status === 'success' ? '成功' : item.status === 'failed' ? '失败' : item.status }}
          </v-chip>
        </template>
        <template #item.created_at="{ item }">
          <span class="text-caption text-medium-emphasis">{{ item.created_at }}</span>
        </template>
        <template #no-data>
          <div class="text-center text-medium-emphasis py-6">暂无数据</div>
        </template>
      </v-data-table-server>
    </v-card>
  </v-container>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { http } from '@/api'
import { useSnackbar } from '@/composables/useSnackbar'
import { useAuthStore } from '@/stores/auth'
import type { PaginatedResponse, PushItem } from '@/types/api'

const snackbar = useSnackbar()
const auth = useAuthStore()

// ── Server list with category ──
interface ServerItem {
  id: number
  name: string
  domain?: string
  status?: string
  category?: string
}
const servers = ref<ServerItem[]>([])


async function loadServers() {
  try {
    const res = await http.get<{ items: ServerItem[] }>('/servers/', { per_page: 200 })
    servers.value = res.items || []
  } catch {
    servers.value = []
  }
}

// ── State ──
const sourcePath = ref('')
const targetPath = ref('')
const serverSearch = ref('')
const selectedIds = ref<Set<number>>(new Set())
const pushing = ref(false)
const pushProgress = ref(0)
const completedCount = ref(0)
const _pushTimers: ReturnType<typeof setTimeout>[] = []
const pushStatus = ref<Record<number, string>>({})

// ── Sync Mode ──
const syncMode = ref<'incremental' | 'full' | 'checksum'>('incremental')
const syncModeDesc = computed(() => {
  switch (syncMode.value) {
    case 'incremental': return '仅传输新增或修改的文件'
    case 'full': return '使目标目录与源目录完全一致，会删除不存在于源的文件'
    case 'checksum': return '通过文件内容校验和判断是否需要传输'
    default: return ''
  }
})

// ── ZIP Upload ──
const isDragging = ref(false)
const uploadedZip = ref<{ name: string; sourcePath: string; fileCount: number; size: number } | null>(null)
const zipUploading = ref(false)
const zipInput = ref<HTMLInputElement | null>(null)

function triggerZipInput() {
  zipInput.value?.click()
}

async function handleZipDrop(e: DragEvent) {
  isDragging.value = false
  const file = e.dataTransfer?.files[0]
  if (file && file.name.endsWith('.zip')) await uploadZip(file)
}

function handleZipSelect(e: Event) {
  const file = (e.target as HTMLInputElement).files?.[0]
  if (file) uploadZip(file)
}

async function uploadZip(file: File) {
  zipUploading.value = true
  try {
    const form = new FormData()
    form.append('file', file)
    const res = await http.upload<{ source_path: string; file_count: number; size: number }>('/sync/upload-zip', form)
    uploadedZip.value = { name: file.name, sourcePath: res.source_path, fileCount: res.file_count, size: res.size }
    snackbar('ZIP 上传成功')
  } catch (e: any) { snackbar(e.message || '上传失败', 'error') }
  finally { zipUploading.value = false }
}

function clearZipUpload() { uploadedZip.value = null }

function formatSize(bytes: number) {
  if (bytes < 1024) return bytes + ' B'
  if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'
  return (bytes / 1048576).toFixed(1) + ' MB'
}

// ── WebSocket Progress ──
interface ServerProgress {
  id: number
  name: string
  status: 'pending' | 'running' | 'success' | 'failed'
  detail?: string
}

const wsProgressItems = ref<ServerProgress[]>([])
const pushBatchId = ref('')
let wsSocket: WebSocket | null = null

function getWsUrl(): string {
  const base = import.meta.env.VITE_API_BASE || ''
  if (base) {
    const wsBase = base.replace(/^http/, 'ws')
    return `${wsBase}/ws/alerts`
  }
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
  return `${proto}//${location.host}/ws/alerts`
}

function connectProgressWs() {
  if (!auth.token) return
  const url = `${getWsUrl()}?token=${auth.token}`
  const socket = new WebSocket(url)

  socket.onmessage = (event) => {
    if (event.data === 'pong') return
    try {
      const msg = JSON.parse(event.data)
      if (msg.type === 'ping') {
        socket.send('pong')
        return
      }
      if (msg.type === 'sync_progress' && msg.batch_id === pushBatchId.value) {
        const idx = wsProgressItems.value.findIndex(p => p.id === msg.server_id)
        if (idx !== -1) {
          const item = wsProgressItems.value[idx]
          if (msg.status === 'running') {
            item.status = 'running'
            item.detail = msg.detail || '同步中...'
          } else if (msg.status === 'success') {
            item.status = 'success'
            item.detail = msg.detail || '完成'
          } else if (msg.status === 'failed') {
            item.status = 'failed'
            item.detail = msg.detail || '失败'
          }
        }
      }
    } catch {
      // Ignore malformed messages
    }
  }

  socket.onclose = () => {
    wsSocket = null
  }

  wsSocket = socket
}

function disconnectProgressWs() {
  if (wsSocket) {
    wsSocket.close()
    wsSocket = null
  }
}

// ── Push Cancel ──
const cancelling = ref(false)

async function cancelPush() {
  if (!pushBatchId.value) return
  cancelling.value = true
  try {
    await http.post('/sync/cancel', { batch_id: pushBatchId.value })
    // Mark all pending/running as cancelled
    wsProgressItems.value.forEach(s => {
      if (s.status === 'pending' || s.status === 'running') {
        s.status = 'failed'
        s.detail = '已取消'
      }
    })
    snackbar('推送已取消')
  } catch (e: unknown) {
    const msg = e instanceof Error ? e.message : '取消失败'
    snackbar(msg, 'error')
  } finally {
    cancelling.value = false
  }
}

// Preview
const showPreview = ref(false)
interface PreviewData { error?: string; files?: number; size?: string }
const previewData = ref<PreviewData>({})

// Logs
const logs = ref<PushItem[]>([])
const logTotal = ref(0)
const logLoading = ref(false)
const logPage = ref(1)

const logHeaders = [
  { title: '时间', key: 'created_at', width: 180 },
  { title: '服务器', key: 'server_name' },
  { title: '状态', key: 'status', width: 100 },
  { title: '源路径', key: 'source_path' },
  { title: '目标路径', key: 'target_path' },
]

// ── Computed ──
const selectAll = computed({
  get: () => servers.value.length > 0 && selectedIds.value.size === servers.value.length,
  set: () => {},
})

const filteredServers = computed(() => {
  if (!serverSearch.value) return servers.value
  const q = serverSearch.value.toLowerCase()
  return servers.value.filter(s => s.name.toLowerCase().includes(q) || (s.domain || '').toLowerCase().includes(q))
})

// ── Server Grouping by Category ──
const serversByCategory = computed(() => {
  const groups: Record<string, ServerItem[]> = {}
  for (const s of filteredServers.value) {
    const cat = s.category || '未分类'
    if (!groups[cat]) groups[cat] = []
    groups[cat].push(s)
  }
  return Object.entries(groups).map(([category, servers]) => ({ category, servers }))
})

function isCategoryAllSelected(category: string): boolean {
  const group = serversByCategory.value.find(g => g.category === category)
  if (!group || group.servers.length === 0) return false
  return group.servers.every(s => selectedIds.value.has(s.id))
}

function toggleCategory(category: string) {
  const group = serversByCategory.value.find(g => g.category === category)
  if (!group) return
  const s = new Set(selectedIds.value)
  const allSelected = group.servers.every(srv => s.has(srv.id))
  for (const srv of group.servers) {
    if (allSelected) s.delete(srv.id)
    else s.add(srv.id)
  }
  selectedIds.value = s
}

// ── Actions ──
/** Effective source path: uploaded ZIP path takes priority over manual input */
function effectiveSourcePath(): string {
  return uploadedZip.value?.sourcePath || sourcePath.value
}

function toggleServer(id: number) {
  const s = new Set(selectedIds.value)
  if (s.has(id)) s.delete(id); else s.add(id)
  selectedIds.value = s
}

function toggleAll() {
  if (selectedIds.value.size === servers.value.length) {
    selectedIds.value = new Set()
  } else {
    selectedIds.value = new Set(servers.value.map(s => s.id))
  }
}

async function doPreview() {
  if (!effectiveSourcePath() || selectedIds.value.size === 0) {
    snackbar('请选择服务器和源路径（或上传 ZIP 文件）', 'warning')
    return
  }
  try {
    previewData.value = await http.post('/sync/preview', {
      source_path: effectiveSourcePath(),
      target_path: targetPath.value,
      server_id: selectedIds.value.values().next().value,
      sync_mode: syncMode.value,
    })
    showPreview.value = true
  } catch (e: any) {
    previewData.value = { error: e.message }
    showPreview.value = true
  }
}

async function doPush() {
  if (!effectiveSourcePath() || selectedIds.value.size === 0) {
    snackbar('请选择服务器和源路径（或上传 ZIP 文件）', 'warning')
    return
  }
  pushing.value = true
  pushProgress.value = 0
  completedCount.value = 0
  pushStatus.value = {}

  // Initialize WebSocket progress tracking
  const ids = [...selectedIds.value]
  wsProgressItems.value = ids.map(id => {
    const srv = servers.value.find(s => s.id === id)
    return { id, name: srv?.name || `#${id}`, status: 'pending' as const }
  })

  // Fire batch push and capture batch_id
  try {
    const res = await http.post<{ batch_id: string }>('/sync/files', {
      source_path: effectiveSourcePath(),
      target_path: targetPath.value,
      server_ids: ids,
      sync_mode: syncMode.value,
    })
    pushBatchId.value = res.batch_id || ''
    if (pushBatchId.value) {
      connectProgressWs()
    }
  } catch (e: any) {
    // Batch push failed — fall back to per-server push (parallel, not sequential)
    const total = ids.length
    const results = await Promise.allSettled(
      ids.map(async (sid) => {
        try {
          await http.post('/sync/files', {
            source_path: effectiveSourcePath(),
            target_path: targetPath.value,
            server_ids: [sid],
            sync_mode: syncMode.value,
          })
          pushStatus.value[sid] = '成功'
          const idx = wsProgressItems.value.findIndex(p => p.id === sid)
          if (idx !== -1) wsProgressItems.value[idx].status = 'success'
        } catch (innerErr) {
          pushStatus.value[sid] = '失败'
          const idx = wsProgressItems.value.findIndex(p => p.id === sid)
          if (idx !== -1) {
            wsProgressItems.value[idx].status = 'failed'
            wsProgressItems.value[idx].detail = (innerErr instanceof Error ? innerErr.message : '推送失败')
          }
        }
        completedCount.value++
        pushProgress.value = Math.round((completedCount.value / total) * 100)
      })
    )
    pushing.value = false
    loadLogs()
    const succeeded = results.filter(r => r.status === 'fulfilled').length
    snackbar(`推送完成：${succeeded}/${total} 台成功`)
    return
  }

  // Monitor WS progress completion
  const checkInterval = setInterval(() => {
    const done = wsProgressItems.value.filter(p => p.status === 'success' || p.status === 'failed').length
    completedCount.value = done
    pushProgress.value = Math.round((done / wsProgressItems.value.length) * 100)

    if (done >= wsProgressItems.value.length) {
      clearInterval(checkInterval)
      pushing.value = false
      disconnectProgressWs()
      loadLogs()
      const failed = wsProgressItems.value.filter(p => p.status === 'failed').length
      const success = wsProgressItems.value.filter(p => p.status === 'success').length
      snackbar(`推送完成：${success} 成功，${failed} 失败`)
    }
  }, 500)
  _pushTimers.push(checkInterval as unknown as ReturnType<typeof setTimeout>)

  // Safety timeout: stop waiting after 5 minutes
  const safetyTimer = setTimeout(() => {
    if (pushing.value) {
      clearInterval(checkInterval)
      pushing.value = false
      disconnectProgressWs()
      loadLogs()
      const done = wsProgressItems.value.filter(p => p.status === 'success' || p.status === 'failed').length
      snackbar(`推送超时，${done}/${wsProgressItems.value.length} 台已完成`, 'warning')
    }
  }, 300000)
  _pushTimers.push(safetyTimer)
}

async function loadLogs() {
  logLoading.value = true
  try {
    const res = await http.get<PaginatedResponse<PushItem>>('/servers/logs', { page: logPage.value, per_page: 15 })
    logs.value = res.items || []
    logTotal.value = res.total || 0
  } catch {
    logs.value = []
  } finally {
    logLoading.value = false
  }
}

function pushColor(status: string) {
  return status === '成功' ? 'success' : status === '失败' ? 'error' : 'warning'
}


onMounted(() => {
  loadServers()
  loadLogs()
})

onUnmounted(() => {
  disconnectProgressWs()
  _pushTimers.forEach(t => clearTimeout(t))
  _pushTimers.length = 0
})

</script>
