/**
 * Watch slot pins — CRUD, WebSocket metrics, countdown.
 */
import { ref, computed, onScopeDispose } from 'vue'
import { http, ApiError } from '@/api'
import { buildWebSocketUrl } from '@/utils/wsUrl'
import { useAuthStore } from '@/stores/auth'
import { formatApiError } from '@/utils/apiError'

import type { WatchTtlMinutes } from '@/constants/watchTtl'

export interface WatchMetrics {
  probe_status?: string
  source?: string
  is_online?: boolean
  cpu_pct?: number | null
  mem_pct?: number | null
  disk_pct?: number | null
  load_1?: number | null
  load_5?: number | null
  load_15?: number | null
  net_up_bps?: number | null
  net_down_bps?: number | null
  disk_read_bps?: number | null
  disk_write_bps?: number | null
  error?: string | null
  processes?: WatchProcess[]
  ts?: string
}

export interface WatchProcess {
  pid?: number
  name?: string
  cpu_pct?: number
  mem_pct?: number
}

export interface WatchSparkPoint {
  ts?: string
  cpu_pct?: number | null
  mem_pct?: number | null
  disk_pct?: number | null
  net_down_bps?: number | null
}

export interface WatchSlot {
  slot_index: number
  empty: boolean
  pin_id?: number
  server_id?: number
  server_name?: string
  session_id?: number
  pinned_at?: string
  expires_at?: string
  remaining_sec?: number
  monitoring_enabled?: boolean
  ttl_minutes?: WatchTtlMinutes
  /** @deprecated 使用 ttl_minutes */
  ttl_hours?: number
  metrics?: WatchMetrics | null
  sparkline?: WatchSparkPoint[]
  processes?: WatchProcess[]
}

const slots = ref<WatchSlot[]>([
  { slot_index: 0, empty: true },
  { slot_index: 1, empty: true },
  { slot_index: 2, empty: true },
  { slot_index: 3, empty: true },
])
const loading = ref(false)
const wsConnected = ref(false)

let ws: WebSocket | null = null
let countdownTimer: ReturnType<typeof setInterval> | null = null
let consumerCount = 0

function applySlots(data: { slots?: WatchSlot[] }) {
  if (Array.isArray(data.slots)) {
    slots.value = data.slots
  }
}

function tickCountdown() {
  let shouldRefresh = false
  for (const slot of slots.value) {
    if (slot.empty || slot.monitoring_enabled === false) continue
    if (slot.remaining_sec == null) continue
    if (slot.remaining_sec <= 0) {
      shouldRefresh = true
      continue
    }
    slot.remaining_sec -= 1
    if (slot.remaining_sec <= 0) {
      shouldRefresh = true
    }
  }
  if (shouldRefresh) {
    void refreshPins()
  }
}

function mergeMetrics(serverId: number, metrics: WatchMetrics) {
  for (const slot of slots.value) {
    if (slot.empty || slot.server_id !== serverId || slot.monitoring_enabled === false) continue
    slot.metrics = { ...(slot.metrics || {}), ...metrics }
    if (metrics.processes?.length) {
      slot.processes = metrics.processes
    }
    const point: WatchSparkPoint = {
      ts: metrics.ts,
      cpu_pct: metrics.cpu_pct,
      mem_pct: metrics.mem_pct,
      disk_pct: metrics.disk_pct,
      net_down_bps: metrics.net_down_bps,
    }
    if (!slot.sparkline) slot.sparkline = []
    slot.sparkline.push(point)
    if (slot.sparkline.length > 180) slot.sparkline.shift()
  }
}

function connectWs() {
  const auth = useAuthStore()
  if (!auth.token || ws?.readyState === WebSocket.OPEN) return
  if (ws) {
    ws.onclose = null
    ws.close()
    ws = null
  }
  const url = buildWebSocketUrl('/ws/watch', { token: auth.token })
  const socket = new WebSocket(url)
  socket.onopen = () => { wsConnected.value = true }
  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 === 'watch_metrics' && msg.server_id && msg.metrics) {
        mergeMetrics(msg.server_id, msg.metrics)
      }
    } catch { /* ignore */ }
  }
  socket.onclose = (ev) => {
    wsConnected.value = false
    ws = null
    if (ev.code !== 4001 && ev.code !== 4401 && auth.isLoggedIn) {
      setTimeout(connectWs, 3000)
    }
  }
  ws = socket
}

function disconnectWs() {
  if (ws) {
    ws.onclose = null
    ws.close()
    ws = null
  }
  wsConnected.value = false
}

async function refreshPins() {
  loading.value = true
  try {
    const data = await http.get<{ slots: WatchSlot[] }>('/watch/pins')
    applySlots(data)
  } finally {
    loading.value = false
  }
}

async function pinServer(
  serverId: number,
  opts?: number | { replaceSlot?: number; slotIndex?: number },
) {
  const options = typeof opts === 'number' ? { replaceSlot: opts } : opts
  try {
    const body: Record<string, number> = { server_id: serverId }
    if (options?.replaceSlot != null) body.replace_slot = options.replaceSlot
    if (options?.slotIndex != null) body.slot_index = options.slotIndex
    const data = await http.post<{ slots: WatchSlot[] }>('/watch/pins', body)
    applySlots(data)
    return { ok: true as const }
  } catch (e: unknown) {
    if (e instanceof ApiError && e.status === 409) {
      return { ok: false as const, full: true }
    }
    return { ok: false as const, full: false, error: formatApiError(e, '加入监测失败') }
  }
}

async function removeSlot(slotIndex: number) {
  const data = await http.delete<{ slots: WatchSlot[] }>(`/watch/pins/${slotIndex}`)
  applySlots(data)
}

async function setSlotMonitoring(slotIndex: number, monitoringEnabled: boolean) {
  const data = await http.patch<{ slots: WatchSlot[] }>(
    `/watch/pins/${slotIndex}/monitoring`,
    { monitoring_enabled: monitoringEnabled },
  )
  applySlots(data)
}

async function setSlotTtl(slotIndex: number, ttlMinutes: WatchTtlMinutes) {
  const data = await http.patch<{ slots: WatchSlot[] }>(
    `/watch/pins/${slotIndex}/ttl`,
    { ttl_minutes: ttlMinutes },
  )
  applySlots(data)
}

export function useWatchPins() {
  consumerCount += 1
  if (consumerCount === 1) {
    if (!countdownTimer) {
      countdownTimer = setInterval(tickCountdown, 1000)
    }
    connectWs()
  }

  onScopeDispose(() => {
    consumerCount = Math.max(0, consumerCount - 1)
    if (consumerCount === 0) {
      disconnectWs()
      if (countdownTimer) {
        clearInterval(countdownTimer)
        countdownTimer = null
      }
    }
  })

  const pinnedMap = computed(() => {
    const map: Record<number, number> = {}
    for (const s of slots.value) {
      if (!s.empty && s.server_id != null) {
        map[s.server_id] = s.slot_index
      }
    }
    return map
  })

  const filledCount = computed(() => slots.value.filter((s) => !s.empty).length)

  return {
    slots,
    loading,
    wsConnected,
    pinnedMap,
    filledCount,
    refreshPins,
    pinServer,
    removeSlot,
    setSlotMonitoring,
    setSlotTtl,
  }
}
