<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
  checkNodeReplicaDrift,
  createComputeNode,
  disableComputeNode,
  drainComputeNode,
  enableComputeNode,
  getComputeGpus,
  getComputeNodes,
  getComputeQueue,
  getComputeSyncJob,
  getNodeReplicas,
  repairNodeReplicas,
  testComputeNode,
  updateComputeNode,
  type ComputeGpu,
  type ComputeNode,
  type ComputeQueueItem,
  type ResourceReplica,
  type ResourceSyncJob,
} from '@/api/modules/compute'
import { statusLabel, statusTagType } from '@/utils/status'

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

const activeTab = ref(String(route.query.tab || 'nodes'))
const loading = ref(true)
const buttonRefreshing = ref(false)
const checkingReplicas = ref(false)
const repairingReplicas = ref(false)
const nodes = ref<ComputeNode[]>([])
const gpus = ref<ComputeGpu[]>([])
const queue = ref<ComputeQueueItem[]>([])
const replicas = ref<ResourceReplica[]>([])
const activeSyncJob = ref<ResourceSyncJob | null>(null)
const selectedNodeId = ref('')
const lastUpdated = ref('')
const nodeDialogVisible = ref(false)
const nodeDialogMode = ref<'create' | 'edit'>('create')
const savingNode = ref(false)
const nodeForm = reactive({
  id: '',
  code: '',
  name: '',
  api_base_url: '',
  file_gateway_url: '',
  enabled: true,
  scheduler_status: 'offline',
  scheduler_weight: 100,
  tags_text: '',
  max_parallel_jobs: 1,
  data_root: '/data/yg-ft',
  model_root: '/data/yg-ft/models',
  log_root: '/opt/yg-ft/logs/training',
  description: '',
})
let timer: ReturnType<typeof setInterval> | null = null
let refreshInFlight = false

const selectedNode = computed(() => nodes.value.find((item) => item.id === selectedNodeId.value))
const enabledNodes = computed(() => nodes.value.filter((item) => item.enabled).length)
const busyGpus = computed(() => gpus.value.filter((item) => item.status === 'busy' || item.status === 'reserved').length)
const totalRunningJobs = computed(() => nodes.value.reduce((sum, item) => sum + item.current_running_jobs, 0))

function asComputeNode(row: unknown): ComputeNode {
  return row as ComputeNode
}

watch(
  () => route.query.tab,
  (tab) => {
    activeTab.value = String(tab || 'nodes')
  },
)

async function load(options: { showLoading?: boolean; showButtonLoading?: boolean } = {}) {
  if (refreshInFlight) return
  refreshInFlight = true
  if (options.showLoading) loading.value = true
  if (options.showButtonLoading) buttonRefreshing.value = true
  try {
    const [nodeList, gpuList, queueList] = await Promise.all([
      getComputeNodes(),
      getComputeGpus(),
      getComputeQueue(),
    ])
    nodes.value = nodeList
    gpus.value = gpuList
    queue.value = queueList
    if ((!selectedNodeId.value || !nodeList.some((item) => item.id === selectedNodeId.value)) && nodeList.length) {
      selectedNodeId.value = nodeList[0].id
    }
    await pollActiveSyncJob()
    await loadReplicas()
    lastUpdated.value = new Date().toLocaleTimeString('zh-CN', { hour12: false })
  } finally {
    if (options.showLoading) loading.value = false
    if (options.showButtonLoading) buttonRefreshing.value = false
    refreshInFlight = false
  }
}

async function pollActiveSyncJob() {
  if (!activeSyncJob.value?.id) return
  try {
    const sync = await getComputeSyncJob(activeSyncJob.value.id)
    activeSyncJob.value = sync
    if (['completed', 'failed', 'stopped', 'cancelled'].includes(sync.status)) {
      await loadReplicas()
    }
  } catch {
    activeSyncJob.value = null
  }
}

async function loadReplicas() {
  if (!selectedNodeId.value) {
    replicas.value = []
    return
  }
  replicas.value = await getNodeReplicas(selectedNodeId.value)
}

async function changeTab(name: string | number) {
  await router.replace({ path: '/compute', query: { tab: String(name) } })
}

async function handleNodeAction(action: 'enable' | 'disable' | 'drain' | 'test', node: ComputeNode) {
  const nodeId = String(node.id)
  if (action === 'enable') await enableComputeNode(nodeId)
  if (action === 'disable') await disableComputeNode(nodeId)
  if (action === 'drain') await drainComputeNode(nodeId)
  if (action === 'test') {
    const result = await testComputeNode(nodeId)
    if (result.success) {
      ElMessage.success(`连接成功，发现 ${result.gpu_count} 张 GPU，延迟 ${result.latency_ms}ms`)
    } else {
      ElMessage.error(result.error || '连接失败')
    }
  }
  await load()
}

async function handleReplicaDriftCheck() {
  if (!selectedNodeId.value) return
  checkingReplicas.value = true
  try {
    const result = await checkNodeReplicaDrift(selectedNodeId.value)
    replicas.value = result.items || []
    ElMessage.success(result.drifted ? `检测完成，发现 ${result.drifted} 个异常副本` : '检测完成，副本状态正常')
  } finally {
    checkingReplicas.value = false
  }
}

async function handleReplicaRepair() {
  if (!selectedNodeId.value) return
  const targets = replicas.value.filter((item) => ['drifted', 'failed', 'repair_pending'].includes(item.sync_status))
  if (!targets.length) {
    ElMessage.info('暂无需要修复的副本')
    return
  }
  repairingReplicas.value = true
  try {
    const result = await repairNodeReplicas(selectedNodeId.value, targets.map((item) => item.id))
    activeSyncJob.value = result.sync
    replicas.value = result.replicas || []
    if (result.async) {
      ElMessage.success('副本修复任务已提交，页面将自动刷新进度')
    } else if (result.failed?.length) {
      ElMessage.warning(`修复完成，失败 ${result.failed.length} 个`)
    } else {
      ElMessage.success('副本修复完成')
    }
    await loadReplicas()
  } finally {
    repairingReplicas.value = false
  }
}

function resetNodeForm() {
  Object.assign(nodeForm, {
    id: '',
    code: '',
    name: '',
    api_base_url: '',
    file_gateway_url: '',
    enabled: true,
    scheduler_status: 'offline',
    scheduler_weight: 100,
    tags_text: '',
    max_parallel_jobs: 1,
    data_root: '/data/yg-ft',
    model_root: '/data/yg-ft/models',
    log_root: '/opt/yg-ft/logs/training',
    description: '',
  })
}

function openCreateNodeDialog() {
  resetNodeForm()
  nodeDialogMode.value = 'create'
  nodeDialogVisible.value = true
}

function openEditNodeDialog(node: ComputeNode) {
  Object.assign(nodeForm, {
    id: node.id,
    code: node.code,
    name: node.name,
    api_base_url: node.api_base_url,
    file_gateway_url: node.file_gateway_url,
    enabled: node.enabled,
    scheduler_status: node.scheduler_status,
    scheduler_weight: node.scheduler_weight,
    tags_text: node.tags?.join(', ') || '',
    max_parallel_jobs: node.max_parallel_jobs,
    data_root: node.data_root,
    model_root: node.model_root,
    log_root: node.log_root,
    description: node.description || '',
  })
  nodeDialogMode.value = 'edit'
  nodeDialogVisible.value = true
}

function buildNodePayload() {
  const tags = nodeForm.tags_text
    .replace(/，/g, ',')
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean)
  return {
    code: nodeForm.code.trim(),
    name: nodeForm.name.trim() || nodeForm.code.trim(),
    api_base_url: nodeForm.api_base_url.trim().replace(/\/$/, ''),
    file_gateway_url: (nodeForm.file_gateway_url || nodeForm.api_base_url).trim().replace(/\/$/, ''),
    enabled: nodeForm.enabled,
    scheduler_status: nodeForm.scheduler_status,
    scheduler_weight: Number(nodeForm.scheduler_weight) || 0,
    tags,
    max_parallel_jobs: Number(nodeForm.max_parallel_jobs) || 1,
    data_root: nodeForm.data_root.trim() || '/data/yg-ft',
    model_root: nodeForm.model_root.trim() || '/data/yg-ft/models',
    log_root: nodeForm.log_root.trim() || '/opt/yg-ft/logs/training',
    description: nodeForm.description.trim(),
  }
}

async function saveNode() {
  const payload = buildNodePayload()
  if (!payload.code || !payload.api_base_url) {
    ElMessage.warning('请填写节点编码和 Compute API 地址')
    return
  }
  savingNode.value = true
  try {
    if (nodeDialogMode.value === 'create') {
      await createComputeNode(payload)
      ElMessage.success('节点已创建')
    } else {
      await updateComputeNode(nodeForm.id, payload)
      ElMessage.success('节点配置已更新')
    }
    nodeDialogVisible.value = false
    await load()
  } finally {
    savingNode.value = false
  }
}

function formatBytes(value?: number) {
  const size = Number(value || 0)
  if (!size) return '-'
  if (size < 1024) return `${size} B`
  if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`
  if (size < 1024 * 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MB`
  return `${(size / 1024 / 1024 / 1024).toFixed(2)} GB`
}

function formatTime(value?: string) {
  return value ? new Date(value).toLocaleString('zh-CN') : '-'
}

onMounted(() => {
  load({ showLoading: true })
  timer = setInterval(() => load(), 5000)
})

onUnmounted(() => {
  if (timer) clearInterval(timer)
})
</script>

<template>
  <section class="compute-page" v-loading="loading">
    <header class="compute-header">
      <div>
        <h1>算力节点</h1>
        <p>维护训练算力节点、GPU 状态、任务队列和模型/数据集本地副本。</p>
      </div>
      <div class="header-actions">
        <span v-if="lastUpdated" class="last-updated">更新 {{ lastUpdated }}</span>
        <el-button type="primary" @click="openCreateNodeDialog">新增节点</el-button>
        <el-button :loading="buttonRefreshing" @click="load({ showButtonLoading: true })">刷新</el-button>
      </div>
    </header>

    <div class="summary-grid">
      <div class="summary-tile"><span>启用节点</span><strong>{{ enabledNodes }} / {{ nodes.length }}</strong></div>
      <div class="summary-tile"><span>GPU 占用</span><strong>{{ busyGpus }} / {{ gpus.length }}</strong></div>
      <div class="summary-tile"><span>运行任务</span><strong>{{ totalRunningJobs }}</strong></div>
      <div class="summary-tile"><span>队列任务</span><strong>{{ queue.length }}</strong></div>
    </div>

    <el-tabs v-model="activeTab" class="compute-tabs" @tab-change="changeTab">
      <el-tab-pane label="节点" name="nodes">
        <el-table :data="nodes" height="100%">
          <el-table-column prop="code" label="节点编码" min-width="150" />
          <el-table-column prop="name" label="节点名称" min-width="150" />
          <el-table-column label="状态" width="120">
            <template #default="{ row }">
              <el-tag :type="statusTagType(row.scheduler_status)">{{ statusLabel(row.scheduler_status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="启用" width="90">
            <template #default="{ row }">
              <el-tag :type="row.enabled ? 'success' : 'info'">{{ row.enabled ? '启用' : '停用' }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="scheduler_weight" label="权重" width="90" />
          <el-table-column label="标签" min-width="220">
            <template #default="{ row }">
              <el-space wrap>
                <el-tag v-for="tag in row.tags" :key="tag" effect="plain">{{ tag }}</el-tag>
              </el-space>
            </template>
          </el-table-column>
          <el-table-column label="任务/GPU" width="120">
            <template #default="{ row }">{{ row.current_running_jobs }} / {{ row.gpu_count }}</template>
          </el-table-column>
          <el-table-column label="服务地址" min-width="260">
            <template #default="{ row }">
              <div class="mono">{{ row.api_base_url }}</div>
              <div class="muted mono">{{ row.file_gateway_url }}</div>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="330" fixed="right">
            <template #default="{ row }">
              <el-button size="small" @click="openEditNodeDialog(asComputeNode(row))">编辑</el-button>
              <el-button size="small" @click="handleNodeAction('test', asComputeNode(row))">测试</el-button>
              <el-button v-if="row.enabled" size="small" @click="handleNodeAction('disable', asComputeNode(row))">停用</el-button>
              <el-button v-else size="small" type="primary" @click="handleNodeAction('enable', asComputeNode(row))">启用</el-button>
              <el-button size="small" type="warning" plain @click="handleNodeAction('drain', asComputeNode(row))">维护</el-button>
            </template>
          </el-table-column>
        </el-table>
      </el-tab-pane>

      <el-tab-pane label="GPU" name="gpus">
        <el-table :data="gpus" height="100%">
          <el-table-column label="GPU" min-width="220">
            <template #default="{ row }">
              <strong>{{ row.node_code }} / GPU {{ row.id }}</strong>
              <div class="muted">{{ row.name }}</div>
            </template>
          </el-table-column>
          <el-table-column label="状态" width="100">
            <template #default="{ row }">
              <el-tag :type="statusTagType(row.status)">{{ statusLabel(row.status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="利用率" min-width="180">
            <template #default="{ row }"><el-progress :percentage="row.gpu_percent" :stroke-width="8" /></template>
          </el-table-column>
          <el-table-column label="显存" min-width="190">
            <template #default="{ row }">
              <el-progress :percentage="Math.round(row.memory_percent)" :stroke-width="8" />
              <span class="muted">{{ row.memory_used_gb }} / {{ row.memory_total_gb }} GB</span>
            </template>
          </el-table-column>
          <el-table-column label="温度/功耗" width="150">
            <template #default="{ row }">{{ row.temperature }} C / {{ row.power_w }} W</template>
          </el-table-column>
          <el-table-column label="进程" min-width="220">
            <template #default="{ row }">
              <span v-if="!row.processes?.length" class="muted">空闲</span>
              <div v-for="process in row.processes" :key="process.pid">
                {{ process.task_name }} <span class="muted">PID {{ process.pid }}</span>
              </div>
            </template>
          </el-table-column>
        </el-table>
      </el-tab-pane>

      <el-tab-pane label="队列" name="queue">
        <el-table :data="queue" height="100%">
          <el-table-column prop="name" label="任务名称" min-width="200" />
          <el-table-column label="状态" width="110">
            <template #default="{ row }">
              <el-tag :type="statusTagType(row.status)">{{ statusLabel(row.status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="进度" min-width="220">
            <template #default="{ row }"><el-progress :percentage="row.progress" :stroke-width="8" /></template>
          </el-table-column>
          <el-table-column prop="compute_node_id" label="节点" width="140" />
          <el-table-column label="GPU" width="120">
            <template #default="{ row }">{{ row.gpus?.join(', ') || '-' }}</template>
          </el-table-column>
          <el-table-column label="创建时间" width="190">
            <template #default="{ row }">{{ formatTime(row.create_time) }}</template>
          </el-table-column>
        </el-table>
      </el-tab-pane>

      <el-tab-pane label="资源副本" name="replicas">
        <div class="replica-toolbar">
          <el-select v-model="selectedNodeId" filterable @change="loadReplicas">
            <el-option v-for="node in nodes" :key="node.id" :label="`${node.code} - ${node.name}`" :value="node.id" />
          </el-select>
          <span v-if="selectedNode" class="muted">当前节点 {{ selectedNode.api_base_url }}</span>
          <el-button :loading="checkingReplicas" @click="handleReplicaDriftCheck">漂移检测</el-button>
          <el-button type="primary" :loading="repairingReplicas" @click="handleReplicaRepair">修复副本</el-button>
        </div>
        <div v-if="activeSyncJob" class="sync-progress">
          <div>
            <strong>同步任务 {{ activeSyncJob.id }}</strong>
            <el-tag :type="statusTagType(activeSyncJob.status)" size="small">{{ statusLabel(activeSyncJob.status) }}</el-tag>
          </div>
          <el-progress :percentage="Number(activeSyncJob.progress || 0)" :stroke-width="8" />
        </div>
        <el-table :data="replicas" height="100%">
          <el-table-column prop="resource_type" label="资源类型" width="110" />
          <el-table-column prop="resource_id" label="资源 ID" min-width="180" />
          <el-table-column prop="local_path" label="本地路径" min-width="300" show-overflow-tooltip />
          <el-table-column label="状态" width="110">
            <template #default="{ row }">
              <el-tag :type="statusTagType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="同步状态" width="120">
            <template #default="{ row }">
              <el-tag :type="statusTagType(row.sync_status)" size="small">{{ statusLabel(row.sync_status) }}</el-tag>
            </template>
          </el-table-column>
          <el-table-column label="大小" width="110">
            <template #default="{ row }">{{ formatBytes(row.byte_size) }}</template>
          </el-table-column>
          <el-table-column label="最后检测" width="190">
            <template #default="{ row }">{{ formatTime(row.last_checked_at) }}</template>
          </el-table-column>
        </el-table>
      </el-tab-pane>
    </el-tabs>

    <el-dialog
      v-model="nodeDialogVisible"
      :title="nodeDialogMode === 'create' ? '新增算力节点' : '编辑算力节点'"
      width="720px"
    >
      <el-form label-width="130px">
        <div class="node-form-grid">
          <el-form-item label="节点编码" required>
            <el-input v-model="nodeForm.code" :disabled="nodeDialogMode === 'edit'" placeholder="gpu-node-01" />
          </el-form-item>
          <el-form-item label="节点名称">
            <el-input v-model="nodeForm.name" placeholder="A800 训练节点 01" />
          </el-form-item>
          <el-form-item label="Compute API" required>
            <el-input v-model="nodeForm.api_base_url" placeholder="http://10.0.0.11:19100" />
          </el-form-item>
          <el-form-item label="File Gateway">
            <el-input v-model="nodeForm.file_gateway_url" placeholder="http://10.0.0.11:19101" />
          </el-form-item>
          <el-form-item label="调度权重">
            <el-input-number v-model="nodeForm.scheduler_weight" :min="0" :max="1000" controls-position="right" />
          </el-form-item>
          <el-form-item label="最大并行任务">
            <el-input-number v-model="nodeForm.max_parallel_jobs" :min="1" :max="32" controls-position="right" />
          </el-form-item>
          <el-form-item label="启用状态">
            <el-switch v-model="nodeForm.enabled" active-text="启用" inactive-text="停用" />
          </el-form-item>
          <el-form-item label="调度状态">
            <el-select v-model="nodeForm.scheduler_status">
              <el-option label="离线" value="offline" />
              <el-option label="在线" value="online" />
              <el-option label="维护中" value="draining" />
              <el-option label="维护模式" value="maintenance" />
            </el-select>
          </el-form-item>
        </div>
        <el-form-item label="标签"><el-input v-model="nodeForm.tags_text" placeholder="a800, lora, beijing" /></el-form-item>
        <el-form-item label="数据根目录"><el-input v-model="nodeForm.data_root" /></el-form-item>
        <el-form-item label="模型根目录"><el-input v-model="nodeForm.model_root" /></el-form-item>
        <el-form-item label="训练日志目录"><el-input v-model="nodeForm.log_root" /></el-form-item>
        <el-form-item label="备注"><el-input v-model="nodeForm.description" type="textarea" :rows="3" /></el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="nodeDialogVisible = false">取消</el-button>
        <el-button type="primary" :loading="savingNode" @click="saveNode">保存</el-button>
      </template>
    </el-dialog>
  </section>
</template>

<style scoped lang="scss">
.compute-page {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 0;
  height: 100%;
  padding: 24px;
  background: #fff;
}

.compute-header {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  align-items: flex-start;

  h1 {
    margin: 0;
    font-size: 24px;
    font-weight: 650;
    color: #111827;
  }

  p {
    margin: 8px 0 0;
    color: #64748b;
  }
}

.header-actions,
.replica-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
}

.last-updated,
.muted {
  color: #64748b;
  font-size: 12px;
}

.last-updated {
  min-width: 92px;
  text-align: right;
}

.summary-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px;
}

.summary-tile {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 14px 16px;
  background: #f8fafc;

  span {
    display: block;
    color: #64748b;
    font-size: 12px;
  }

  strong {
    display: block;
    margin-top: 8px;
    color: #111827;
    font-size: 24px;
  }
}

.compute-tabs {
  flex: 1;
  min-height: 0;

  :deep(.el-tabs__content) {
    height: calc(100% - 56px);
  }

  :deep(.el-tab-pane) {
    height: 100%;
  }
}

.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
}

.replica-toolbar {
  margin-bottom: 12px;

  .el-select {
    width: 280px;
  }
}

.sync-progress {
  display: grid;
  gap: 8px;
  margin-bottom: 12px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 12px 14px;
  background: #f8fafc;

  > div {
    display: flex;
    align-items: center;
    gap: 10px;
  }
}

.node-form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: 12px;

  :deep(.el-input-number),
  :deep(.el-select) {
    width: 100%;
  }
}

@media (max-width: 960px) {
  .compute-header,
  .header-actions,
  .replica-toolbar {
    flex-direction: column;
    align-items: stretch;
  }

  .summary-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .node-form-grid {
    grid-template-columns: 1fr;
  }
}
</style>
