/**
 * Global script execution progress queue — fed by /ws/alerts script_* messages
 * with 3s polling fallback when WebSocket is disconnected.
 */
import { ref, computed, onUnmounted } from 'vue'
import { http } from '@/api'
import { isExecTerminalStatus } from '@/utils/scriptExecutionLabels'

export interface ScriptExecQueueItem {
  executionId: number
  label: string
  status: string
  total: number
  done: number
  remaining: number
  success: number
  failed: number
  progress: string
  longTask: boolean
  operator?: string
  taskKind?: 'script' | 'server_batch'
  op?: string
}

export interface ScriptExecCompleteAlert {
  executionId: number
  label: string
  status: string
  success: number
  failed: number
  total: number
  fading: boolean
  taskKind?: 'script' | 'server_batch'
  op?: string
}

const POLL_MS = 3000
const activeItems = ref<Map<number, ScriptExecQueueItem>>(new Map())
const completeAlerts = ref<ScriptExecCompleteAlert[]>([])
const wsConnected = ref(true)

let pollTimer: ReturnType<typeof setInterval> | null = null
const completeListeners = new Set<(alert: ScriptExecCompleteAlert) => void>()

function mapToItem(msg: Record<string, unknown>): ScriptExecQueueItem {
  const total = Number(msg.total) || 0
  const done = Number(msg.done) || 0
  const remaining = Number(msg.remaining) ?? Math.max(0, total - done)
  const taskKind = msg.task_kind === 'server_batch' ? 'server_batch' : 'script'
  return {
    executionId: Number(msg.execution_id),
    label: String(msg.label || `执行 #${msg.execution_id}`),
    status: String(msg.status || 'running'),
    total,
    done,
    remaining,
    success: Number(msg.success) || 0,
    failed: Number(msg.failed) || 0,
    progress: String(msg.progress || `${done}/${total}`),
    longTask: Boolean(msg.long_task),
    operator: msg.operator ? String(msg.operator) : undefined,
    taskKind,
    op: msg.op ? String(msg.op) : undefined,
  }
}

function upsertItem(item: ScriptExecQueueItem) {
  const next = new Map(activeItems.value)
  if (isExecTerminalStatus(item.status)) {
    next.delete(item.executionId)
  } else {
    next.set(item.executionId, item)
  }
  activeItems.value = next
}

function pushCompleteAlert(item: ScriptExecQueueItem) {
  const alert: ScriptExecCompleteAlert = {
    executionId: item.executionId,
    label: item.label,
    status: item.status,
    success: item.success,
    failed: item.failed,
    total: item.total,
    fading: false,
    taskKind: item.taskKind,
    op: item.op,
  }
  completeAlerts.value = [alert, ...completeAlerts.value].slice(0, 5)
  for (const fn of completeListeners) {
    try {
      fn(alert)
    } catch {
      /* ignore */
    }
  }
  setTimeout(() => {
    completeAlerts.value = completeAlerts.value.map(a =>
      a.executionId === alert.executionId ? { ...a, fading: true } : a,
    )
    setTimeout(() => {
      completeAlerts.value = completeAlerts.value.filter(
        a => a.executionId !== alert.executionId,
      )
    }, 400)
  }, 3000)
}

function ensurePolling() {
  if (pollTimer) return
  pollTimer = setInterval(() => {
    if (wsConnected.value) return
    void pollActive()
  }, POLL_MS)
}

function stopPollingIfIdle() {
  if (activeItems.value.size > 0) return
  if (pollTimer) {
    clearInterval(pollTimer)
    pollTimer = null
  }
}

async function pollActive() {
  const ids = [...activeItems.value.keys()]
  if (!ids.length) {
    stopPollingIfIdle()
    return
  }
  for (const id of ids) {
    try {
      const prev = activeItems.value.get(id)
      const isServerBatch = prev?.taskKind === 'server_batch'
      const res = isServerBatch
        ? await http.get<{
            job_id: number
            status: string
            progress?: string
            label?: string
            op?: string
            total?: number
            done?: number
            remaining?: number
            success?: number
            failed?: number
          }>(`/servers/batch/jobs/${id}`)
        : await http.get<{
            id: number
            status: string
            progress?: string
            long_task?: boolean
            label?: string
            server_ids?: string
            results?: Record<string, unknown>
          }>(`/scripts/executions/${id}`)
      const label = res.label || prev?.label || `执行 #${id}`
      let total = prev?.total || 0
      if (isServerBatch) {
        const batchRes = res as {
          total?: number
          done?: number
          remaining?: number
          success?: number
          failed?: number
          op?: string
        }
        if (typeof batchRes.total === 'number') total = batchRes.total
        const progress = res.progress || prev?.progress || `0/${total}`
        const done = Number(batchRes.done) || 0
        const totalFromProgress = batchRes.total || total
        const item: ScriptExecQueueItem = {
          executionId: id,
          label,
          status: res.status,
          total: totalFromProgress || total,
          done,
          remaining: Number(batchRes.remaining) || Math.max(0, (totalFromProgress || total) - done),
          success: Number(batchRes.success) || 0,
          failed: Number(batchRes.failed) || 0,
          progress,
          longTask: false,
          operator: prev?.operator,
          taskKind: 'server_batch',
          op: batchRes.op,
        }
        if (isExecTerminalStatus(res.status)) {
          upsertItem(item)
          pushCompleteAlert({ ...item, status: res.status })
        } else {
          upsertItem(item)
        }
        continue
      }
      const scriptRes = res as {
        server_ids?: string
        long_task?: boolean
      }
      if (scriptRes.server_ids) {
        try {
          const parsed = JSON.parse(scriptRes.server_ids) as unknown
          if (Array.isArray(parsed)) total = parsed.length
        } catch {
          /* keep */
        }
      }
      const progress = res.progress || prev?.progress || `0/${total}`
      const parts = progress.split('/')
      const success = Number(parts[0]) || 0
      const totalFromProgress = Number(parts[1]) || total
      const item: ScriptExecQueueItem = {
        executionId: id,
        label,
        status: res.status,
        total: totalFromProgress || total,
        done: success,
        remaining: Math.max(0, (totalFromProgress || total) - success),
        success,
        failed: 0,
        progress,
        longTask: Boolean(scriptRes.long_task ?? prev?.longTask),
        operator: prev?.operator,
        taskKind: 'script',
        op: prev?.op,
      }
      if (isExecTerminalStatus(res.status)) {
        upsertItem(item)
        pushCompleteAlert({ ...item, status: res.status })
      } else {
        upsertItem(item)
      }
    } catch {
      /* keep last known */
    }
  }
  stopPollingIfIdle()
}

export function handleScriptWsMessage(msg: Record<string, unknown>) {
  const item = mapToItem(msg)
  if (!item.executionId) return
  if (msg.type === 'script_complete' || isExecTerminalStatus(item.status)) {
    upsertItem(item)
    pushCompleteAlert(item)
    stopPollingIfIdle()
    return
  }
  upsertItem(item)
  ensurePolling()
}

export function setScriptQueueWsConnected(connected: boolean) {
  wsConnected.value = connected
  if (!connected && activeItems.value.size > 0) {
    ensurePolling()
  }
}

export function registerScriptExecution(
  executionId: number,
  label: string,
  totalServers: number,
  longTask = false,
  progress?: string,
  status = 'running',
) {
  if (isExecTerminalStatus(status)) return
  const item: ScriptExecQueueItem = {
    executionId,
    label,
    status,
    total: totalServers,
    done: 0,
    remaining: totalServers,
    success: 0,
    failed: 0,
    progress: progress || `0/${totalServers}`,
    longTask,
    taskKind: 'script',
  }
  upsertItem(item)
  ensurePolling()
}

export function registerServerBatchJob(
  jobId: number,
  label: string,
  totalServers: number,
  op?: string,
  progress?: string,
  status = 'running',
) {
  if (isExecTerminalStatus(status)) return
  const item: ScriptExecQueueItem = {
    executionId: jobId,
    label,
    status,
    total: totalServers,
    done: 0,
    remaining: totalServers,
    success: 0,
    failed: 0,
    progress: progress || `0/${totalServers}`,
    longTask: false,
    taskKind: 'server_batch',
    op,
  }
  upsertItem(item)
  ensurePolling()
}

export function onScriptExecutionComplete(listener: (alert: ScriptExecCompleteAlert) => void) {
  completeListeners.add(listener)
  return () => completeListeners.delete(listener)
}

export function dismissCompleteAlert(executionId: number) {
  completeAlerts.value = completeAlerts.value.map(a =>
    a.executionId === executionId ? { ...a, fading: true } : a,
  )
  setTimeout(() => {
    completeAlerts.value = completeAlerts.value.filter(a => a.executionId !== executionId)
  }, 400)
}

export function useScriptExecutionQueue() {
  const runningItems = computed(() =>
    [...activeItems.value.values()].sort((a, b) => b.executionId - a.executionId),
  )

  onUnmounted(() => {
    /* singleton — do not stop global poll on component unmount */
  })

  return {
    runningItems,
    completeAlerts: computed(() => completeAlerts.value),
    registerScriptExecution,
    dismissCompleteAlert,
    onScriptExecutionComplete,
  }
}
