import { ref, computed } from 'vue'
import { http } from '@/api'
import { useSnackbar } from '@/composables/useSnackbar'
import { useAuthStore } from '@/stores/auth'
import { buildWebSocketUrl } from '@/utils/wsUrl'
import type { usePushForm } from './usePushForm'
import type { usePushServers } from './usePushServers'
import { formatPushErrorMessage } from './labels'
import {
  TERMINAL_PROGRESS_STATUSES,
  type ServerProgress,
  type ServerProgressStatus,
  type SyncProgressMsg,
} from './types'

type PushForm = ReturnType<typeof usePushForm>
type PushServers = ReturnType<typeof usePushServers>

type PushFilesResponse = {
  batch_id?: string
  results?: Record<
    string,
    { status?: string; error_message?: string | null; duration_seconds?: number | null }
  >
}

function newPushBatchId(): string {
  return crypto.randomUUID().replace(/-/g, '').slice(0, 12)
}

export function usePushProgress(
  form: PushForm,
  servers: PushServers,
  onPushFinished: () => void,
) {
  const snackbar = useSnackbar()
  const auth = useAuthStore()

  const pushing = ref(false)
  const cancelling = ref(false)
  const wsProgressItems = ref<ServerProgress[]>([])
  const pushBatchId = ref('')
  const wsSocket = ref<WebSocket | null>(null)

  const _pushTimeouts: ReturnType<typeof setTimeout>[] = []

  const doneCount = computed(() =>
    wsProgressItems.value.filter(p => TERMINAL_PROGRESS_STATUSES.includes(p.status)).length,
  )

  const totalProgress = computed(() => {
    const n = wsProgressItems.value.length
    return n ? Math.round((doneCount.value / n) * 100) : 0
  })

  function clearPushTimers() {
    _pushTimeouts.forEach(t => clearTimeout(t))
    _pushTimeouts.length = 0
  }

  function checkPushComplete() {
    const total = wsProgressItems.value.length
    if (!total || !pushing.value) return
    const done = wsProgressItems.value.filter(p =>
      TERMINAL_PROGRESS_STATUSES.includes(p.status),
    ).length
    if (done < total) return

    pushing.value = false
    disconnectProgressWs()
    onPushFinished()
    const failed = wsProgressItems.value.filter(p => p.status === 'failed').length
    const cancelled = wsProgressItems.value.filter(p => p.status === 'cancelled').length
    const success = wsProgressItems.value.filter(p => p.status === 'success').length
    snackbar(`推送完成：${success} 成功，${failed} 失败${cancelled ? `，${cancelled} 取消` : ''}`)
  }

  function applySyncProgressMsg(msg: SyncProgressMsg) {
    if (msg.type !== 'sync_progress' || msg.batch_id !== pushBatchId.value || msg.server_id == null) {
      return
    }
    const idx = wsProgressItems.value.findIndex(p => p.id === msg.server_id)
    if (idx === -1) return

    const item = wsProgressItems.value[idx]
    const st = msg.status

    if (st === 'running') {
      item.status = 'running'
      item.detail = '同步中...'
    } else if (st === 'success') {
      item.status = 'success'
      const sec = msg.duration_seconds
      item.detail = sec != null ? `完成（${sec}s）` : '完成'
      servers.pushStatus.value[msg.server_id] = '成功'
    } else if (st === 'failed') {
      item.status = 'failed'
      item.detail = formatPushErrorMessage(msg.error_message) || '失败'
      servers.pushStatus.value[msg.server_id] = '失败'
    } else if (st === 'cancelled') {
      item.status = 'cancelled'
      item.detail = formatPushErrorMessage(msg.error_message) || '已取消'
      servers.pushStatus.value[msg.server_id] = '已取消'
    }

    checkPushComplete()
  }

  function applyPushResultsFromHttp(res: PushFilesResponse) {
    const results = res.results
    if (!results) return
    for (const [sid, log] of Object.entries(results)) {
      const serverId = Number(sid)
      if (!Number.isFinite(serverId)) continue
      const idx = wsProgressItems.value.findIndex(p => p.id === serverId)
      if (idx === -1) continue
      const item = wsProgressItems.value[idx]
      const st = log.status
      if (st === 'success') {
        item.status = 'success'
        const sec = log.duration_seconds
        item.detail = sec != null ? `完成（${sec}s）` : '完成'
        servers.pushStatus.value[serverId] = '成功'
      } else if (st === 'failed') {
        item.status = 'failed'
        item.detail = formatPushErrorMessage(log.error_message) || '失败'
        servers.pushStatus.value[serverId] = '失败'
      } else if (st === 'cancelled') {
        item.status = 'cancelled'
        item.detail = formatPushErrorMessage(log.error_message) || '已取消'
        servers.pushStatus.value[serverId] = '已取消'
      }
    }
    checkPushComplete()
  }

  function connectProgressWs(): Promise<void> {
    if (!auth.token) {
      return Promise.reject(new Error('未登录，无法连接推送进度'))
    }
    disconnectProgressWs()
    const url = buildWebSocketUrl('/ws/sync', { token: auth.token })
    const socket = new WebSocket(url)

    return new Promise((resolve, reject) => {
      socket.onopen = () => resolve()
      socket.onerror = () => reject(new Error('推送进度 WebSocket 连接失败'))

      socket.onmessage = (event) => {
        if (event.data === 'pong') return
        try {
          const msg = JSON.parse(event.data) as SyncProgressMsg & { type?: string }
          if (msg.type === 'ping') {
            socket.send('pong')
            return
          }
          applySyncProgressMsg(msg)
        } catch {
          // Ignore malformed messages
        }
      }

      socket.onclose = (ev) => {
        wsSocket.value = null
        if (ev.code === 4001 || ev.code === 4401) {
          auth.forceLogout()
          return
        }
        if (ev.code !== 1000 && pushing.value) {
          wsProgressItems.value.forEach(p => {
            if (p.status === 'pending' || p.status === 'running') {
              p.status = 'failed'
              p.detail = '连接中断'
              servers.pushStatus.value[p.id] = '失败'
            }
          })
          pushing.value = false
          snackbar('推送进度连接中断，部分服务器状态未知', 'warning')
          onPushFinished()
        }
      }

      wsSocket.value = socket
    })
  }

  function disconnectProgressWs() {
    if (wsSocket.value) {
      wsSocket.value.close(1000)
      wsSocket.value = null
    }
  }

  async function cancelPush() {
    if (!pushBatchId.value) return
    cancelling.value = true
    try {
      await http.post('/sync/cancel', { batch_id: pushBatchId.value })
      wsProgressItems.value.forEach(s => {
        if (s.status === 'pending' || s.status === 'running') {
          s.status = 'cancelled'
          s.detail = '已取消'
          servers.pushStatus.value[s.id] = '已取消'
        }
      })
      pushing.value = false
      disconnectProgressWs()
      onPushFinished()
      snackbar('推送已取消')
    } catch (e: unknown) {
      snackbar(e instanceof Error ? e.message : '取消失败', 'error')
    } finally {
      cancelling.value = false
    }
  }

  async function doPush() {
    if (pushing.value) return
    if (!form.validateForm(servers.selectedIds.value.size)) return

    clearPushTimers()
    disconnectProgressWs()
    pushBatchId.value = ''
    servers.pushStatus.value = {}

    const ids = [...servers.selectedIds.value]
    wsProgressItems.value = ids.map(id => {
      const srv = servers.servers.value.find(s => s.id === id)
      return { id, name: srv?.name || `#${id}`, status: 'pending' as ServerProgressStatus }
    })

    pushing.value = true
    pushBatchId.value = newPushBatchId()

    try {
      await connectProgressWs()
      const res = await http.post<PushFilesResponse>('/sync/files', {
        source_path: form.effectiveSourcePath(),
        target_path: form.targetPath.value,
        server_ids: ids,
        sync_mode: form.syncMode.value,
        batch_id: pushBatchId.value,
      })
      if (res.batch_id && res.batch_id !== pushBatchId.value) {
        pushBatchId.value = res.batch_id
      }
      applyPushResultsFromHttp(res)
    } catch (e: unknown) {
      disconnectProgressWs()
      const msg = e instanceof Error ? e.message : '推送请求失败'
      wsProgressItems.value.forEach(p => {
        p.status = 'failed'
        p.detail = msg
      })
      pushing.value = false
      snackbar(msg, 'error')
      onPushFinished()
      return
    }

    const safetyTimer = setTimeout(() => {
      if (!pushing.value) return
      wsProgressItems.value.forEach(p => {
        if (p.status === 'pending' || p.status === 'running') {
          p.status = 'failed'
          p.detail = '等待超时'
          servers.pushStatus.value[p.id] = '失败'
        }
      })
      pushing.value = false
      disconnectProgressWs()
      onPushFinished()
      snackbar(`推送超时，${doneCount.value}/${wsProgressItems.value.length} 台已有结果`, 'warning')
    }, 300_000)
    _pushTimeouts.push(safetyTimer)
  }

  function dispose() {
    disconnectProgressWs()
    clearPushTimers()
  }

  return {
    pushing,
    cancelling,
    wsProgressItems,
    pushBatchId,
    doneCount,
    totalProgress,
    doPush,
    cancelPush,
    dispose,
  }
}
