<template>
  <v-card elevation="0" rounded="lg" border class="fleet-trends-card">
    <v-card-item>
      <template #title>
        <span class="text-h6">舰队资源趋势</span>
      </template>
      <template #subtitle>
        <span class="text-medium-emphasis">
          全库 Agent 均值 · 每 {{ intervalMinutes }} 分钟采样 · 北京时间
          <span v-if="lastUpdated"> · 更新于 {{ lastUpdated }}</span>
        </span>
      </template>
      <template #append>
        <div class="d-flex align-center flex-wrap ga-2">
          <v-btn-toggle
            v-model="hours"
            density="compact"
            variant="outlined"
            divided
            mandatory
            @update:model-value="onHoursChange"
          >
            <v-btn :value="24" size="small">24 小时</v-btn>
            <v-btn :value="168" size="small">7 天</v-btn>
          </v-btn-toggle>
          <v-btn
            size="small"
            variant="tonal"
            prepend-icon="mdi-fullscreen"
            :disabled="!hasData"
            @click="fullscreen = true"
          >
            全屏
          </v-btn>
          <v-btn
            icon="mdi-refresh"
            size="small"
            variant="text"
            :loading="loading"
            title="刷新"
            @click="loadAll()"
          />
        </div>
      </template>
    </v-card-item>

    <v-card-text class="pt-0">
      <div v-if="loading" class="py-2">
        <v-skeleton-loader type="heading" class="mb-3" />
        <v-skeleton-loader type="image" height="360" />
      </div>
      <div v-else-if="!hasData" class="text-center py-12">
        <v-icon icon="mdi-chart-timeline-variant" size="48" color="disabled" class="mb-3" />
        <div class="text-body-1 text-medium-emphasis">暂无历史采样数据</div>
        <div class="text-caption text-medium-emphasis mt-1">
          心跳落库后约 {{ intervalMinutes }} 分钟出现首条；满 24 小时后曲线更完整
        </div>
      </div>
      <div v-else>
        <div class="d-flex align-center flex-wrap ga-2 mb-3">
          <v-chip size="small" variant="outlined" label>
            {{ viewScopeLabel }}
          </v-chip>
          <v-chip v-if="isZoomed" size="small" color="primary" variant="tonal" label closable @click:close="resetZoom">
            已缩放视图 · 点击关闭恢复全览
          </v-chip>
        </div>

        <FleetTrendKpiStrip :items="kpiItems" class="mb-4" />

        <FleetTrendEChart
          :key="chartKey"
          :points="points"
          :hours="hours"
          :thresholds="thresholds"
          :zoom-start="zoomStart"
          :zoom-end="zoomEnd"
          @range-change="onRangeChange"
        />
      </div>
    </v-card-text>
  </v-card>

  <v-dialog v-model="fullscreen" fullscreen transition="dialog-bottom-transition">
    <v-card>
      <v-toolbar density="comfortable" border>
        <v-toolbar-title>舰队资源趋势 · {{ hours >= 168 ? '7 天' : '24 小时' }}</v-toolbar-title>
        <v-spacer />
        <v-btn icon="mdi-close" @click="fullscreen = false" />
      </v-toolbar>
      <v-card-text class="pa-4 pa-md-6">
        <FleetTrendKpiStrip :items="kpiItems" class="mb-4" />
        <FleetTrendEChart
          :points="points"
          :hours="hours"
          :thresholds="thresholds"
          :height="420"
          :zoom-start="zoomStart"
          :zoom-end="zoomEnd"
          @range-change="onRangeChange"
        />
        <v-divider class="my-4" />
        <div class="text-subtitle-2 mb-2">采样明细（{{ points.length }} 条）</div>
        <v-data-table
          :headers="tableHeaders"
          :items="tableRows"
          :items-per-page="15"
          density="compact"
          height="320"
          fixed-header
          class="fleet-trend-table"
        >
          <template #item.recorded_at="{ item }">
            {{ formatPointTimeBeijing(item.recorded_at) }}
          </template>
          <template #item.cpu_avg="{ item }">
            <span :class="thresholdClass(item.cpu_avg, thresholds.cpu)">{{ fmtPct(item.cpu_avg) }}</span>
          </template>
          <template #item.mem_avg="{ item }">
            <span :class="thresholdClass(item.mem_avg, thresholds.mem)">{{ fmtPct(item.mem_avg) }}</span>
          </template>
          <template #item.disk_avg="{ item }">
            <span :class="thresholdClass(item.disk_avg, thresholds.disk)">{{ fmtPct(item.disk_avg) }}</span>
          </template>
          <template #item.coverage="{ item }">
            {{ formatCoverage(item) }}
          </template>
          <template #item.alert_count="{ item }">
            <v-chip
              v-if="(item.alert_count ?? 0) > 0"
              size="x-small"
              color="error"
              variant="tonal"
              label
            >
              {{ item.alert_count }}
            </v-chip>
            <span v-else class="text-medium-emphasis">0</span>
          </template>
        </v-data-table>
      </v-card-text>
    </v-card>
  </v-dialog>
</template>

<script setup lang="ts">
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'
import { http } from '@/api'
import type { SettingsResponse } from '@/types/api'
import FleetTrendKpiStrip, { type FleetKpiItem } from '@/components/dashboard/FleetTrendKpiStrip.vue'
import {
  computeRangeStats,
  formatPointTimeBeijing,
  indexRangeFromZoom,
  parseAlertThresholds,
  type FleetAlertThresholds,
  type FleetTrendPoint,
} from '@/utils/fleetTrendAnalytics'
import { formatDateTimeBeijing } from '@/utils/datetime'

const FleetTrendEChart = defineAsyncComponent(
  () => import('@/components/dashboard/FleetTrendEChart.vue'),
)

interface FleetMetricsResponse {
  hours: number
  interval_minutes: number
  points: FleetTrendPoint[]
}

const hours = ref(24)
const loading = ref(true)
const points = ref<FleetTrendPoint[]>([])
const intervalMinutes = ref(10)
const lastUpdated = ref('')
const fullscreen = ref(false)
const chartKey = ref(0)

const thresholds = ref<FleetAlertThresholds>({ cpu: 80, mem: 80, disk: 80 })

const zoomStart = ref<number | undefined>(undefined)
const zoomEnd = ref<number | undefined>(undefined)
const viewStartPercent = ref(0)
const viewEndPercent = ref(100)

const hasData = computed(() => points.value.length >= 2)

const isZoomed = computed(
  () => viewStartPercent.value > 0.5 || viewEndPercent.value < 99.5,
)

const viewIndexRange = computed(() =>
  indexRangeFromZoom(points.value.length, viewStartPercent.value, viewEndPercent.value),
)

const viewStats = computed(() =>
  computeRangeStats(points.value, viewIndexRange.value.startIdx, viewIndexRange.value.endIdx),
)

const viewScopeLabel = computed(() => {
  const { startIdx, endIdx } = viewIndexRange.value
  const n = endIdx - startIdx + 1
  if (!isZoomed.value) {
    return hours.value >= 168 ? `全周期 ${n} 条采样` : `近 24 小时 · ${n} 条`
  }
  const from = formatPointTimeBeijing(points.value[startIdx]?.recorded_at ?? '').slice(5, 16)
  const to = formatPointTimeBeijing(points.value[endIdx]?.recorded_at ?? '').slice(5, 16)
  return `视图 ${from} — ${to} · ${n} 条`
})

const kpiItems = computed((): FleetKpiItem[] => [
  {
    key: 'cpu',
    label: 'CPU',
    unit: '%',
    current: viewStats.value.cpu.current,
    delta: viewStats.value.cpu.delta,
    min: viewStats.value.cpu.min,
    max: viewStats.value.cpu.max,
    avg: viewStats.value.cpu.avg,
    threshold: thresholds.value.cpu,
  },
  {
    key: 'mem',
    label: '内存',
    unit: '%',
    current: viewStats.value.mem.current,
    delta: viewStats.value.mem.delta,
    min: viewStats.value.mem.min,
    max: viewStats.value.mem.max,
    avg: viewStats.value.mem.avg,
    threshold: thresholds.value.mem,
  },
  {
    key: 'disk',
    label: '磁盘',
    unit: '%',
    current: viewStats.value.disk.current,
    delta: viewStats.value.disk.delta,
    min: viewStats.value.disk.min,
    max: viewStats.value.disk.max,
    avg: viewStats.value.disk.avg,
    threshold: thresholds.value.disk,
  },
  {
    key: 'online',
    label: '在线',
    unit: '台',
    current: viewStats.value.online.current,
    delta: viewStats.value.online.delta,
    min: viewStats.value.online.min,
    max: viewStats.value.online.max,
    avg: viewStats.value.online.avg,
  },
])

const tableHeaders = [
  { title: '采样时间', key: 'recorded_at', sortable: false },
  { title: 'CPU', key: 'cpu_avg', align: 'end' as const, width: 72 },
  { title: '内存', key: 'mem_avg', align: 'end' as const, width: 72 },
  { title: '磁盘', key: 'disk_avg', align: 'end' as const, width: 72 },
  { title: '在线', key: 'online_count', align: 'end' as const, width: 64 },
  { title: '采样数', key: 'sample_count', align: 'end' as const, width: 72 },
  { title: '覆盖率', key: 'coverage', align: 'end' as const, width: 72, sortable: false },
  { title: '告警', key: 'alert_count', align: 'end' as const, width: 56 },
]

const tableRows = computed(() => [...points.value].reverse())

function fmtPct(v: number | null | undefined): string {
  return v != null ? `${v}%` : '—'
}

function formatCoverage(p: FleetTrendPoint): string {
  if (p.sample_count == null || p.online_count <= 0) return '—'
  return `${Math.round((p.sample_count / p.online_count) * 100)}%`
}

function thresholdClass(v: number | null | undefined, threshold: number): string {
  if (v == null) return ''
  if (v >= threshold) return 'text-error font-weight-medium'
  if (v >= threshold - 10) return 'text-warning'
  return ''
}

function onRangeChange(payload: { startPercent: number; endPercent: number }) {
  viewStartPercent.value = payload.startPercent
  viewEndPercent.value = payload.endPercent
}

function resetZoom() {
  zoomStart.value = hours.value >= 168 ? 65 : 0
  zoomEnd.value = 100
  viewStartPercent.value = zoomStart.value
  viewEndPercent.value = 100
  chartKey.value += 1
}

function onHoursChange() {
  resetZoom()
  void loadTrends()
}

async function loadThresholds() {
  try {
    const res = await http.get<SettingsResponse>('/settings/')
    thresholds.value = parseAlertThresholds(Array.isArray(res) ? res : [])
  } catch {
    /* 默认 80% */
  }
}

async function loadTrends() {
  loading.value = true
  try {
    const data = await http.get<FleetMetricsResponse>('/servers/fleet-metrics', { hours: hours.value })
    points.value = data.points || []
    intervalMinutes.value = data.interval_minutes || 10
    lastUpdated.value = formatDateTimeBeijing(new Date().toISOString()).slice(11, 19)
  } catch {
    points.value = []
  } finally {
    loading.value = false
  }
}

async function loadAll() {
  await Promise.all([loadTrends(), loadThresholds()])
}

onMounted(() => {
  resetZoom()
  void loadAll()
})

defineExpose({ reload: loadAll })
</script>

<style scoped>
.fleet-trends-card :deep(.v-card-item) {
  padding-bottom: 8px;
}

.fleet-trend-table :deep(th) {
  white-space: nowrap;
}
</style>
