<template>
  <v-container fluid class="pa-6">
    <v-card elevation="0" border rounded="lg">
      <v-card-title class="d-flex align-center text-h6">
        推送调度
        <v-spacer />
        <v-btn color="primary" variant="flat" size="small" prepend-icon="mdi-plus" @click="openEditor(null)">新建调度</v-btn>
      </v-card-title>

      <v-data-table-server
        :items="schedules"
        :headers="headers"
        :items-length="totalItems"
        :loading="loading"
        :page="page"
        :items-per-page="itemsPerPage"
        :items-per-page-options="dataTablePageOptions"
        hover
        density="comfortable"
        @update:page="onPageChange"
        @update:items-per-page="onItemsPerPageChange"
      >
        <template #item.schedule_type="{ item }">
          <v-chip size="small" variant="tonal" :color="item.schedule_type === 'script' ? 'secondary' : 'primary'" label>
            {{ item.schedule_type === 'script' ? '脚本' : '推送' }}
          </v-chip>
        </template>

        <template #item.run_mode="{ item }">
          <span class="text-caption">{{ item.run_mode === 'once' ? '单次' : '循环' }}</span>
        </template>

        <template #item.cron_expr="{ item }">
          <v-chip size="small" variant="tonal" label prepend-icon="mdi-clock-outline">
            {{ formatScheduleCycleDisplay(item.run_mode, item.cron_expr, item.fire_at) }}
          </v-chip>
        </template>

        <template #item.source_path="{ item }">
          <span v-if="item.schedule_type === 'script'" class="text-caption">{{ scriptSourceLabel(item) }}</span>
          <span v-else class="text-caption">{{ item.source_path || '—' }}</span>
        </template>

        <template #item.enabled="{ item }">
          <v-switch
            v-model="item.enabled"
            color="primary"
            density="compact"
            hide-details
            :loading="togglingId === item.id"
            @update:model-value="toggleEnabled(item)"
          />
        </template>

        <template #item.server_count="{ item }">
          <span class="text-medium-emphasis">{{ serverIdCount(item.server_ids) }} 台</span>
        </template>

        <template #item.last_run_at="{ item }">
          <span class="text-caption text-medium-emphasis">{{ item.last_run_at ? formatDateTimeBeijing(item.last_run_at) : '从未运行' }}</span>
        </template>

        <template #item.next_run="{ item }">
          <span class="text-caption text-medium-emphasis">{{ item.next_run ? formatDateTimeBeijing(item.next_run) : '—' }}</span>
        </template>

        <template #item.actions="{ item }">
          <v-btn variant="text" size="x-small" density="compact" @click="openEditor(item)">编辑</v-btn>
          <v-btn variant="text" size="x-small" density="compact" :loading="runningId === item.id" @click="runNow(item)">立即执行</v-btn>
          <v-btn variant="text" size="x-small" color="error" density="compact" @click="confirmDelete(item)">删除</v-btn>
        </template>
        <template #no-data>
          <div class="text-center text-medium-emphasis py-6">暂无计划任务</div>
        </template>
      </v-data-table-server>
    </v-card>

    <v-dialog v-model="showEditor" max-width="800" scrollable>
      <v-card border>
        <v-card-title>{{ editingSchedule ? '编辑调度' : '新建调度' }}</v-card-title>
        <v-card-text>
          <v-alert
            v-if="saveError"
            type="error"
            density="compact"
            variant="tonal"
            class="mb-3"
            closable
            @click:close="saveError = ''"
          >
            {{ saveError }}
          </v-alert>

          <v-text-field v-model="form.name" label="调度名称" variant="outlined" density="compact" class="mb-2" />

          <v-btn-toggle v-model="form.schedule_type" mandatory density="compact" class="mb-3" color="primary">
            <v-btn value="push" size="small">文件推送</v-btn>
            <v-btn value="script" size="small">脚本执行</v-btn>
          </v-btn-toggle>

          <v-btn-toggle v-model="form.run_mode" mandatory density="compact" class="mb-3" color="primary">
            <v-btn value="once" size="small">单次执行</v-btn>
            <v-btn value="cron" size="small">循环执行</v-btn>
          </v-btn-toggle>

          <ScheduleCyclePicker
            v-model="form.cycle"
            :single-shot="form.run_mode === 'once'"
            class="mb-2"
          />

          <v-alert type="info" density="compact" variant="tonal" class="mb-3 text-caption">
            单次执行选「当天」时:分；循环执行再选重复方式。同步模式、目标路径等按系统与各服务器配置。
          </v-alert>

          <template v-if="form.schedule_type === 'push'">
            <PushZipUpload
              class="mb-2"
              :uploaded-source="pushSource.uploadedSource"
              :validated-source="pushSource.validatedSource"
              v-model:source-path="pushSource.sourcePath"
              :source-path-validating="pushSource.sourcePathValidating"
              :is-dragging="pushSource.isDragging"
              :source-uploading="pushSource.sourceUploading"
              @dragover="pushSource.isDragging = true"
              @dragleave="pushSource.isDragging = false"
              @drop="pushSource.handleFileDrop"
              @file-select="pushSource.handleFileSelect"
              @zip-extract-select="pushSource.handleZipExtractSelect"
              @clear="pushSource.clearUploadedSource"
              @clear-validated="pushSource.clearValidatedSource"
              @validate="pushSource.validateSourcePath"
            />

            <PushStagingPanel
              v-if="pushSource.uploadedSource"
              class="mb-2"
              :root-path="pushSource.uploadedSource.sourcePath"
              :relative-path="pushStaging.relativePath"
              :entries="pushStaging.entries"
              :loading="pushStaging.loading"
              :selected-count="pushStaging.selectedCount"
              :all-selected="pushStaging.allSelected"
              :some-selected="pushStaging.someSelected"
              :is-selected="pushStaging.isSelected"
              v-model:rename-open="pushStaging.renameOpen"
              v-model:rename-value="pushStaging.renameValue"
              :rename-loading="pushStaging.renameLoading"
              v-model:delete-open="pushStaging.deleteOpen"
              :delete-targets="pushStaging.deleteTargets"
              :delete-loading="pushStaging.deleteLoading"
              @refresh="pushStaging.refreshCurrent()"
              @go-root="pushStaging.goRoot"
              @open-dir="pushStaging.openDirectory"
              @preview="pushStaging.previewFile"
              @rename="pushStaging.openRename"
              @delete="pushStaging.openDelete"
              @batch-delete="pushStaging.openBatchDelete"
              @toggle-select="pushStaging.toggleSelect"
              @toggle-select-all="pushStaging.toggleSelectAll"
              @confirm-rename="pushStaging.confirmRename"
              @confirm-delete="pushStaging.confirmDelete"
            />

            <PushStagingPreviewDialog
              v-model="pushStaging.previewOpen"
              :loading="pushStaging.previewLoading"
              :result="pushStaging.previewResult"
            />
          </template>

          <template v-else>
            <v-select
              v-model="form.script_id"
              :items="scriptList"
              item-title="name"
              item-value="id"
              label="脚本库（可选）"
              variant="outlined"
              density="compact"
              clearable
              class="mb-2"
            />
            <v-textarea
              v-model="form.script_content"
              label="命令内容（无脚本库时必填）"
              variant="outlined"
              density="compact"
              rows="4"
              auto-grow
              class="mb-2"
            />
          </template>

          <div class="text-caption text-medium-emphasis mb-1">目标服务器（按分类选择）</div>
          <ServerCategoryPicker
            v-model="editorServerIds"
            :servers="serverList"
            :loading="serversLoading"
            max-height="320px"
            class="mb-2"
          />
          <v-switch v-model="form.enabled" label="启用" color="primary" density="compact" hide-details />
        </v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="showEditor = false">取消</v-btn>
          <v-btn color="primary" variant="flat" @click="saveSchedule" :loading="saving">保存</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>

    <v-dialog v-model="showDelete" max-width="400">
      <v-card border>
        <v-card-title>确认删除</v-card-title>
        <v-card-text>确定要删除调度 <strong>{{ deletingSchedule?.name }}</strong> 吗？</v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="showDelete = false">取消</v-btn>
          <v-btn color="error" variant="flat" @click="doDelete">删除</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-container>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import { usePageActivateRefresh } from '@/composables/usePageActivateRefresh'
import { http } from '@/api'
import { useServerList } from '@/composables/useServerList'
import { useSnackbar } from '@/composables/useSnackbar'
import { showScriptSubmitToast } from '@/composables/useScriptSubmitToast'
import { usePushForm } from '@/composables/push/usePushForm'
import { usePushStaging } from '@/composables/push/usePushStaging'
import { formatApiError } from '@/utils/apiError'
import ScheduleCyclePicker from '@/components/schedules/ScheduleCyclePicker.vue'
import ServerCategoryPicker from '@/components/servers/ServerCategoryPicker.vue'
import PushZipUpload from '@/components/push/PushZipUpload.vue'
import PushStagingPanel from '@/components/push/PushStagingPanel.vue'
import PushStagingPreviewDialog from '@/components/push/PushStagingPreviewDialog.vue'
import type { ScheduleItem, ScriptItem } from '@/types/api'
import { DATA_TABLE_ITEMS_PER_PAGE_OPTIONS, headersWithoutSort, sliceClientPage } from '@/constants/dataTable'
import {
  buildCronFromCycle,
  computeNextCronRun,
  cycleFromSchedule,
  defaultCycleConfig,
  formatScheduleCycleDisplay,
  validateCycleConfig,
  type ScheduleCycleConfig,
} from '@/utils/scheduleCycle'
import { formatDateTimeBeijing } from '@/utils/datetime'

defineOptions({ name: 'SchedulesPage' })

const dataTablePageOptions = [...DATA_TABLE_ITEMS_PER_PAGE_OPTIONS]
const snackbar = useSnackbar()
const { servers: serverList, loading: serversLoading, loadServers } = useServerList()

const pushSourceRaw = usePushForm()
const pushStagingRaw = usePushStaging(pushSourceRaw.uploadedSource)
const pushSource = reactive(pushSourceRaw)
const pushStaging = reactive(pushStagingRaw)

function resetPushSource() {
  pushSource.clearUploadedSource()
  pushSource.clearValidatedSource()
}

async function loadPushSourceFromPath(path: string) {
  resetPushSource()
  const p = path.trim()
  if (!p) return
  pushSource.sourcePath = p
  await pushSource.validateSourcePath()
}

const loading = ref(false)
const togglingId = ref<number | null>(null)
const runningId = ref<number | null>(null)
const schedules = ref<ScheduleItem[]>([])
const scriptList = ref<ScriptItem[]>([])

const page = ref(1)
const totalItems = ref(0)
const itemsPerPage = ref(20)

const showEditor = ref(false)
const editingSchedule = ref<ScheduleItem | null>(null)
const saving = ref(false)
const saveError = ref('')
const editorServerIds = ref<number[]>([])
const form = ref({
  name: '',
  schedule_type: 'push' as 'push' | 'script',
  run_mode: 'once' as 'cron' | 'once',
  cycle: defaultCycleConfig() as ScheduleCycleConfig,
  script_id: null as number | null,
  script_content: '',
  enabled: true,
})

const showDelete = ref(false)
const deletingSchedule = ref<ScheduleItem | null>(null)

const headers = headersWithoutSort([
  { title: '名称', key: 'name' },
  { title: '类型', key: 'schedule_type', width: 80 },
  { title: '模式', key: 'run_mode', width: 72 },
  { title: '开始执行周期', key: 'cron_expr', width: 180 },
  { title: '源文件 / 脚本', key: 'source_path' },
  { title: '服务器', key: 'server_count', width: 80 },
  { title: '上次运行', key: 'last_run_at', width: 140 },
  { title: '下次运行', key: 'next_run', width: 140 },
  { title: '启用', key: 'enabled', width: 80 },
  { title: '操作', key: 'actions', width: 200, align: 'end' as const },
])

function parseServerIds(raw: unknown): number[] {
  if (Array.isArray(raw)) return raw.map((x) => Number(x)).filter((n) => !Number.isNaN(n))
  if (typeof raw === 'string' && raw.trim()) {
    try {
      const parsed = JSON.parse(raw)
      if (Array.isArray(parsed)) return parsed.map((x) => Number(x)).filter((n) => !Number.isNaN(n))
    } catch { /* ignore */ }
  }
  return []
}

function serverIdCount(raw: unknown): number {
  return parseServerIds(raw).length
}

function scriptSourceLabel(item: ScheduleItem): string {
  if (item.script_id) {
    const script = scriptList.value.find((s) => s.id === item.script_id)
    if (script) return script.name
    return `脚本 #${item.script_id}`
  }
  const content = item.script_content?.trim()
  if (content) {
    return content.length > 48 ? `${content.slice(0, 48)}…` : content
  }
  return '—'
}

function normalizeSchedule(row: ScheduleItem): ScheduleItem {
  return { ...row, server_ids: parseServerIds(row.server_ids) }
}

function buildSchedulePayload(): Record<string, unknown> {
  const ids = editorServerIds.value
  if (!form.value.name.trim()) throw new Error('请填写调度名称')
  if (!ids.length) throw new Error('请至少选择一台服务器')

  const payload: Record<string, unknown> = {
    name: form.value.name.trim(),
    schedule_type: form.value.schedule_type,
    run_mode: form.value.run_mode,
    server_ids: JSON.stringify(ids),
    enabled: form.value.enabled,
  }

  const cycleErr = validateCycleConfig(form.value.cycle)
  if (cycleErr) throw new Error(cycleErr)
  const cronExpr = buildCronFromCycle(form.value.cycle)
  payload.cron_expr = cronExpr

  if (form.value.run_mode === 'once') {
    const next = computeNextCronRun(cronExpr)
    if (!next) throw new Error('无法计算单次执行时间，请检查开始执行周期')
    payload.fire_at = next.toISOString()
  } else {
    payload.fire_at = null
  }

  if (form.value.schedule_type === 'push') {
    const src = pushSource.effectiveSourcePath()
    if (!src) {
      if (pushSource.sourcePath.trim() && !pushSource.validatedSource) {
        throw new Error('请先点击「验证路径」确认 Nexus 主机上的源目录')
      }
      throw new Error('请上传源文件或验证 Nexus 主机源路径')
    }
    payload.source_path = src
  } else {
    if (!form.value.script_id && !form.value.script_content.trim()) {
      throw new Error('脚本调度须选择脚本库或填写命令内容')
    }
    if (form.value.script_id) payload.script_id = form.value.script_id
    if (form.value.script_content.trim()) payload.script_content = form.value.script_content.trim()
  }

  return payload
}

function onPageChange(p: number) {
  page.value = p
  loadSchedules()
}

function onItemsPerPageChange(n: number) {
  itemsPerPage.value = n
  page.value = 1
  loadSchedules()
}

async function loadScripts() {
  try {
    const res = await http.getList<ScriptItem>('/scripts/')
    scriptList.value = res.items || []
  } catch {
    scriptList.value = []
  }
}

async function loadSchedules(silent = false) {
  if (!silent) loading.value = true
  try {
    const res = await http.getList<ScheduleItem>('/schedules/')
    const all = (res.items || []).map(normalizeSchedule)
    totalItems.value = all.length
    schedules.value = sliceClientPage(all, page.value, itemsPerPage.value)
  } catch (e: unknown) {
    schedules.value = []
    totalItems.value = 0
    snackbar(formatApiError(e, '加载调度列表失败'), 'error')
  } finally {
    if (!silent) loading.value = false
  }
}

function openEditor(schedule: ScheduleItem | null) {
  editingSchedule.value = schedule
  saveError.value = ''
  resetPushSource()
  if (schedule) {
    editorServerIds.value = parseServerIds(schedule.server_ids)
    form.value = {
      name: schedule.name,
      schedule_type: (schedule.schedule_type === 'script' ? 'script' : 'push'),
      run_mode: (schedule.run_mode === 'cron' ? 'cron' : 'once'),
      cycle: cycleFromSchedule(schedule.cron_expr, schedule.fire_at),
      script_id: schedule.script_id ?? null,
      script_content: schedule.script_content || '',
      enabled: schedule.enabled,
    }
    if (form.value.schedule_type === 'push' && schedule.source_path) {
      void loadPushSourceFromPath(schedule.source_path)
    }
  } else {
    editorServerIds.value = []
    form.value = {
      name: '',
      schedule_type: 'push',
      run_mode: 'once',
      cycle: defaultCycleConfig(),
      script_id: null,
      script_content: '',
      enabled: true,
    }
  }
  showEditor.value = true
}

async function saveSchedule() {
  saveError.value = ''
  saving.value = true
  try {
    const payload = buildSchedulePayload()
    if (editingSchedule.value) await http.put(`/schedules/${editingSchedule.value.id}`, payload)
    else await http.post('/schedules/', payload)
    showEditor.value = false
    loadSchedules()
    snackbar('保存成功')
  } catch (e: unknown) {
    const msg = formatApiError(e, '保存失败')
    saveError.value = msg
    snackbar(msg, 'error')
  } finally {
    saving.value = false
  }
}

async function toggleEnabled(item: ScheduleItem) {
  togglingId.value = item.id
  try {
    await http.put(`/schedules/${item.id}`, { enabled: item.enabled })
    loadSchedules()
  } catch (e: unknown) {
    item.enabled = !item.enabled
    snackbar(formatApiError(e, '操作失败'), 'error')
  } finally {
    togglingId.value = null
  }
}

async function runNow(item: ScheduleItem) {
  runningId.value = item.id
  try {
    const serverIds = parseServerIds(item.server_ids)
    if (item.schedule_type === 'script') {
      await http.post('/scripts/exec', {
        server_ids: serverIds,
        script_id: item.script_id ?? undefined,
        command: item.script_content || undefined,
      })
      showScriptSubmitToast('已触发脚本执行')
    } else {
      await http.post('/sync/files', {
        source_path: item.source_path,
        server_ids: serverIds,
        sync_mode: 'incremental',
      })
      snackbar('已触发推送')
    }
  } catch (e: unknown) {
    snackbar(formatApiError(e, '执行失败'), 'error')
  } finally {
    runningId.value = null
  }
}

function confirmDelete(item: ScheduleItem) {
  deletingSchedule.value = item
  showDelete.value = true
}

async function doDelete() {
  if (!deletingSchedule.value) return
  try {
    await http.delete(`/schedules/${deletingSchedule.value.id}`)
    showDelete.value = false
    loadSchedules()
    snackbar('已删除')
  } catch (e: unknown) {
    snackbar(formatApiError(e, '删除失败'), 'error')
  }
}

async function refreshSchedulesPage(silent = false) {
  await Promise.all([
    loadSchedules(silent),
    loadServers({ all: true }),
    loadScripts(),
  ])
}

usePageActivateRefresh((silent) => refreshSchedulesPage(silent))
</script>
