import { ref, computed, watch, nextTick, type Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import { WebLinksAddon } from '@xterm/addon-web-links'
import { http } from '@/api'
import { useSnackbar } from '@/composables/useSnackbar'
import {
  formatQuickCmdForSend,
  sortQuickCommands,
  type QuickCmdItem,
} from '@/composables/useTerminalQuickCommands'
import { buildWebSocketUrl } from '@/utils/wsUrl'
import {
  createNative,
  deleteNative,
  getNative,
  setNativeFitAddon,
  setNativeResizeObserver,
  setNativeTerm,
  isSessionWsOpen,
  forEachNative,
} from './termNativeStore'
import {
  isSessionReady,
  sanitizeRemotePath,
  type TermSessionData,
  type TerminalServerItem,
} from './types'
import { useTerminalSettings } from './useTerminalSettings'
import { useTerminalCmdBar } from './useTerminalCmdBar'
import { XTERM_CLASSIC_THEME } from './xtermTheme'
import { decodeTerminalPayload } from '@/utils/terminalPayload'
import { fetchPagePerPage } from '@/utils/paginatedFetch'
import type { ServerApiItem } from '@/types/api'
import { useServerSearchHistory } from '@/composables/useServerSearchHistory'

const MAX_TABS = 10

export function useTerminalSessions(nexusDrawer: Ref<boolean>) {
  const route = useRoute()
  const router = useRouter()
  const snackbar = useSnackbar()
  const settings = useTerminalSettings()

  const sessions = ref<TermSessionData[]>([])
  const activeSessionId = ref('')
  const termContainer = ref<HTMLElement | null>(null)
  const termRefs = new Map<string, HTMLElement>()
  const showServerDialog = ref(false)
  const servers = ref<TerminalServerItem[]>([])
  const serversLoading = ref(false)
  const serverSearch = ref('')
  const quickCmds = ref<QuickCmdItem[]>([])
  const { items: terminalSearchHistory, loadHistoryOnly, record: recordTerminalSearch } =
    useServerSearchHistory('terminal')

  const activeSession = computed(
    () => sessions.value.find((s) => s.id === activeSessionId.value) || null,
  )

  const activeSessionConnected = computed(() => {
    const s = activeSession.value
    return s ? isSessionReady(s) : false
  })

  const cmdBarDisabled = computed(() => !activeSession.value)

  function parseRouteServerId(): number {
    const raw = route.query.server_id ?? route.query.server
    const id = Number(raw)
    return Number.isFinite(id) && id > 0 ? id : 0
  }

  async function waitForTermContainer(sessionId: string, attempts = 24): Promise<HTMLElement | null> {
    for (let i = 0; i < attempts; i += 1) {
      const el = termRefs.get(sessionId)
      if (el) return el
      await nextTick()
    }
    return termRefs.get(sessionId) ?? null
  }

  const filteredServers = computed(() => {
    const q = serverSearch.value.toLowerCase().trim()
    if (!q) return servers.value
    return servers.value.filter(
      (s) => s.name.toLowerCase().includes(q) || s.domain.toLowerCase().includes(q),
    )
  })

  function toTerminalServerItem(row: Pick<ServerApiItem, 'id' | 'name' | 'domain' | 'is_online'>): TerminalServerItem {
    return {
      id: row.id,
      name: row.name,
      domain: row.domain,
      is_online: row.is_online,
    }
  }

  async function resolveServerMeta(serverId: number): Promise<TerminalServerItem | null> {
    const cached = servers.value.find((s) => s.id === serverId)
    if (cached) return cached
    try {
      const row = await http.get<ServerApiItem>(`/servers/${serverId}`)
      const item = toTerminalServerItem(row)
      if (!servers.value.some((s) => s.id === item.id)) {
        servers.value = [...servers.value, item]
      }
      return item
    } catch {
      return null
    }
  }

  const showEmptyPicker = computed(
    () => sessions.value.length === 0 && !showServerDialog.value,
  )

  function getSession(sessionId: string): TermSessionData | undefined {
    return sessions.value.find((t) => t.id === sessionId)
  }

  function sendWsData(sessionId: string, data: string): boolean {
    const ws = getNative(sessionId)?.ws
    if (!ws || ws.readyState !== WebSocket.OPEN) return false
    ws.send(JSON.stringify({ type: 'DATA', data }))
    return true
  }

  const cmdBar = useTerminalCmdBar(
    () => activeSession.value,
    sendWsData,
    () => snackbar('终端连接中，请稍候', 'warning'),
  )

  function refitAllTerminals() {
    forEachNative((_id, native) => {
      try {
        native.fitAddon?.fit()
      } catch {
        /* container size 0 */
      }
    })
  }

  watch(nexusDrawer, () => {
    nextTick(() => {
      refitAllTerminals()
      setTimeout(refitAllTerminals, 220)
    })
  })

  function setTermRef(el: unknown, sessionId: string) {
    if (el) termRefs.set(sessionId, el as HTMLElement)
  }

  function createTerminal(): Terminal {
    return new Terminal({
      cursorBlink: true,
      cursorStyle: 'bar',
      fontSize: settings.fontSize.value,
      scrollback: settings.scrollback.value,
      fontFamily: '"Cascadia Code","Fira Code","JetBrains Mono",Menlo,monospace',
      theme: XTERM_CLASSIC_THEME,
      allowProposedApi: true,
      drawBoldTextInBrightColors: true,
    })
  }

  function sendCdToSession(s: TermSessionData, path: string) {
    const dir = sanitizeRemotePath(path)
    if (!dir || !isSessionReady(s)) return
    sendWsData(s.id, `cd ${dir}\r`)
    s.initialCdSent = true
  }

  function stopUptimeCounter(s: TermSessionData) {
    const native = getNative(s.id)
    if (native?.uptimeTimer) {
      clearInterval(native.uptimeTimer)
      native.uptimeTimer = null
    }
    s.uptime = ''
  }

  function updateUptime(s: TermSessionData) {
    if (!s.connectionStartTime) {
      s.uptime = ''
      return
    }
    const sec = Math.floor((Date.now() - s.connectionStartTime) / 1000)
    const h = Math.floor(sec / 3600)
    const m = Math.floor((sec % 3600) / 60)
    const ss = sec % 60
    s.uptime = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(ss).padStart(2, '0')}`
  }

  function startUptimeCounter(s: TermSessionData) {
    s.connectionStartTime = Date.now()
    stopUptimeCounter(s)
    updateUptime(s)
    const native = getNative(s.id)
    if (!native) return
    native.uptimeTimer = setInterval(() => updateUptime(s), 1000)
  }

  function stopPing(s: TermSessionData) {
    const native = getNative(s.id)
    if (native?.pingTimer) {
      clearInterval(native.pingTimer)
      native.pingTimer = null
    }
    s.pingMs = null
  }

  function startPing(s: TermSessionData) {
    stopPing(s)
    s.pingMs = null
    const native = getNative(s.id)
    if (!native) return
    native.pingTimer = setInterval(() => {
      if (isSessionWsOpen(s.id)) {
        getNative(s.id)?.ws?.send(JSON.stringify({ type: 'PING', ts: Date.now() }))
      }
    }, 5000)
  }

  const SSH_INIT_TIMEOUT_MS = 45_000

  function clearInitTimeout(sessionId: string) {
    const native = getNative(sessionId)
    if (native?.initTimeout) {
      clearTimeout(native.initTimeout)
      native.initTimeout = null
    }
  }

  function disposeSessionNative(sessionId: string) {
    const native = getNative(sessionId)
    if (!native) return
    clearInitTimeout(sessionId)
    if (native.reconnectTimer) {
      clearTimeout(native.reconnectTimer)
      native.reconnectTimer = null
    }
    if (native.pingTimer) {
      clearInterval(native.pingTimer)
      native.pingTimer = null
    }
    if (native.uptimeTimer) {
      clearInterval(native.uptimeTimer)
      native.uptimeTimer = null
    }
    if (native.ws) {
      try {
        native.ws.close(1000)
      } catch {
        /* already closed */
      }
      native.ws = null
    }
    if (native.term) {
      try {
        native.term.dispose()
      } catch {
        /* already disposed */
      }
      native.term = null
    }
    if (native.resizeObserver) {
      try {
        native.resizeObserver.disconnect()
      } catch {
        /* ignore */
      }
      native.resizeObserver = null
    }
    native.fitAddon = null
    deleteNative(sessionId)
  }

  async function connectSession(
    sessionId: string,
    opts?: { websshTokenPromise?: Promise<{ webssh_token: string }> },
  ) {
    const s = getSession(sessionId)
    if (!s) return
    const native = getNative(sessionId)
    if (!native) return

    if (native.ws) {
      try {
        native.ws.close(1000)
      } catch {
        /* ignore */
      }
      native.ws = null
    }

    s.status = 'connecting'

    let websshToken: string
    try {
      const res = opts?.websshTokenPromise
        ? await opts.websshTokenPromise
        : await http.post<{ webssh_token: string }>('/auth/webssh-token', {
            server_id: s.serverId,
          })
      websshToken = res.webssh_token
    } catch (e: unknown) {
      const msg = e instanceof Error ? e.message : '无法获取终端令牌'
      s.status = 'error'
      native.term?.writeln(`\r\n\x1b[31m⚠ ${msg}\x1b[0m`)
      s.showOverlay = true
      s.overlayMsg = msg
      s.reconnectCountdown = ''
      return
    }

    const wsUrl = buildWebSocketUrl(`/ws/terminal/${s.serverId}`, { token: websshToken })
    const ws = new WebSocket(wsUrl)
    native.ws = ws

    ws.onopen = () => {
      s.status = 'connecting'
      s.manualDisconnect = false
      s.reconnectAttempt = 0
      if (native.reconnectTimer) {
        clearTimeout(native.reconnectTimer)
        native.reconnectTimer = null
      }
      s.showOverlay = false
      s.overlayMsg = ''
      s.reconnectCountdown = ''
      clearInitTimeout(sessionId)
      native.initTimeout = setTimeout(() => {
        if (s.status !== 'connecting') return
        s.status = 'error'
        s.showOverlay = true
        s.overlayMsg = 'SSH 连接超时，请重试'
        s.reconnectCountdown = ''
        native.term?.writeln('\r\n\x1b[31m⚠ SSH 连接超时\x1b[0m')
        try {
          ws.close(4000, 'SSH init timeout')
        } catch {
          /* ignore */
        }
      }, SSH_INIT_TIMEOUT_MS)
      native.term?.focus()
    }

    ws.onmessage = (e) => {
      try {
        const msg = JSON.parse(e.data as string)
        switch (msg.type) {
          case 'TERMINAL_INIT':
            clearInitTimeout(sessionId)
            s.status = 'connected'
            s.serverName = msg.server_name || s.serverName
            s.showOverlay = false
            s.overlayMsg = ''
            startUptimeCounter(s)
            startPing(s)
            {
              const dims = native.fitAddon?.proposeDimensions()
              if (dims && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({ type: 'RESIZE', cols: dims.cols, rows: dims.rows }))
              }
            }
            if (s.pendingPath && !s.initialCdSent && isSessionReady(s)) {
              sendCdToSession(s, s.pendingPath)
            }
            native.term?.focus()
            break
          case 'DATA':
            native.term?.write(decodeTerminalPayload(msg))
            break
          case 'ERROR':
            native.term?.write(`\r\n\x1b[31m⚠ ${msg.message}\x1b[0m\r\n`)
            break
          case 'PONG':
            s.pingMs = Date.now() - (msg.ts || 0)
            break
          case 'CLOSE':
            native.term?.write('\r\n\x1b[33m━━ 连接已关闭 ━━\x1b[0m\r\n')
            break
        }
      } catch {
        native.term?.write(e.data)
      }
    }

    ws.onclose = (ev) => {
      clearInitTimeout(sessionId)
      stopPing(s)
      stopUptimeCounter(s)

      if (ev.code === 4001) {
        native.term?.write('\r\n\x1b[31m⚠ 认证失败，请重新登录\x1b[0m\r\n')
        setTimeout(() => router.push('/login'), 2000)
        return
      }
      if (ev.code === 4003) {
        s.status = 'error'
        native.term?.write(
          `\r\n\x1b[31m⚠ 授权失败: ${ev.reason || 'SSH凭据未配置'}\x1b[0m\r\n`,
        )
        s.showOverlay = true
        s.overlayMsg = ev.reason || 'SSH 凭据未配置'
        s.reconnectCountdown = ''
        return
      }

      s.status = 'error'
      native.ws = null

      if (!s.manualDisconnect && ev.code !== 1000 && ev.code !== 4001 && ev.code !== 4003) {
        startReconnect(s)
      } else {
        s.showOverlay = true
        s.overlayMsg = '连接已断开'
        s.reconnectCountdown = ''
      }
    }

    ws.onerror = () => {
      /* onclose handles UI */
    }
  }

  function startReconnect(s: TermSessionData) {
    const native = getNative(s.id)
    if (!native) return
    s.reconnectAttempt++
    const delay = Math.min(1000 * 2 ** (s.reconnectAttempt - 1), 30000)
    s.showOverlay = true
    s.overlayMsg = '连接已断开'
    s.reconnectCountdown = `${Math.ceil(delay / 1000)} 秒后自动重连…`
    native.reconnectTimer = setTimeout(() => {
      void doReconnect(s)
    }, delay)
  }

  async function doReconnect(s: TermSessionData) {
    const native = getNative(s.id)
    if (!native) return
    s.showOverlay = false
    s.reconnectCountdown = ''
    s.manualDisconnect = false
    s.reconnectAttempt = 0
    native.term?.clear()
    await connectSession(s.id)
  }

  function reconnect() {
    const s = activeSession.value
    if (!s) return
    const native = getNative(s.id)
    if (native?.reconnectTimer) {
      clearTimeout(native.reconnectTimer)
      native.reconnectTimer = null
    }
    s.reconnectAttempt = 0
    void doReconnect(s)
  }

  function disconnect() {
    const s = activeSession.value
    if (!s) return
    const native = getNative(s.id)
    if (!native) return
    s.manualDisconnect = true
    if (native.reconnectTimer) {
      clearTimeout(native.reconnectTimer)
      native.reconnectTimer = null
    }
    if (native.ws) {
      if (native.ws.readyState === WebSocket.OPEN) {
        native.ws.send(JSON.stringify({ type: 'CLOSE' }))
        native.ws.close(1000, 'User disconnect')
      }
      native.ws = null
    }
    stopPing(s)
    stopUptimeCounter(s)
    s.status = 'error'
    s.showOverlay = true
    s.overlayMsg = '连接已断开'
    s.reconnectCountdown = ''
  }

  async function switchTab(sessionId: string) {
    activeSessionId.value = sessionId
    await nextTick()
    const native = getNative(sessionId)
    if (native?.fitAddon) {
      setTimeout(() => {
        try {
          native.fitAddon!.fit()
        } catch {
          /* size 0 */
        }
      }, 50)
    }
    native?.term?.focus()
  }

  async function closeTab(sessionId: string) {
    const idx = sessions.value.findIndex((s) => s.id === sessionId)
    if (idx < 0) return

    disposeSessionNative(sessionId)
    termRefs.delete(sessionId)
    sessions.value.splice(idx, 1)

    if (sessions.value.length === 0) {
      activeSessionId.value = ''
      return
    }
    const newIdx = Math.min(idx, sessions.value.length - 1)
    await switchTab(sessions.value[newIdx].id)
  }

  async function newSession(serverId?: number, opts?: { path?: string }) {
    if (sessions.value.length >= MAX_TABS) {
      snackbar('最多10个标签', 'warning')
      return
    }
    if (!serverId) {
      showServerDialog.value = true
      return
    }

    const existing = sessions.value.find((s) => s.serverId === serverId)
    if (existing) {
      snackbar('该服务器已有会话，已切换过去', 'info')
      await switchTab(existing.id)
      if (opts?.path) sendCdToSession(existing, opts.path)
      return
    }

    const websshTokenPromise = http.post<{ webssh_token: string }>('/auth/webssh-token', {
      server_id: serverId,
    })
    const server = await resolveServerMeta(serverId)
    const name = server?.name ?? `#${serverId}`
    const sid = `term_${Date.now()}`

    const session: TermSessionData = {
      id: sid,
      serverId,
      serverName: name,
      cwd: '',
      cmdHistory: cmdBar.loadHistoryForNewSession(),
      cmdIdx: -1,
      reconnectAttempt: 0,
      manualDisconnect: false,
      status: 'connecting',
      showOverlay: false,
      overlayMsg: '',
      reconnectCountdown: '',
      uptime: '',
      pingMs: null,
      connectionStartTime: 0,
      pendingPath: sanitizeRemotePath(opts?.path || ''),
      initialCdSent: false,
    }

    createNative(sid)
    sessions.value.push(session)

    await switchTab(sid)

    const container = await waitForTermContainer(sid)
    if (!container) {
      snackbar('终端面板未就绪，请关闭标签后重试', 'error')
      await closeTab(sid)
      return
    }

    const term = createTerminal()
    const fitAddon = new FitAddon()
    term.loadAddon(fitAddon)
    term.loadAddon(new WebLinksAddon())
    term.open(container)

    setNativeTerm(sid, term)
    setNativeFitAddon(sid, fitAddon)

    term.onData((data) => {
      const sess = getSession(sid)
      if (sess && isSessionReady(sess)) sendWsData(sid, data)
    })

    term.onResize(({ cols, rows }) => {
      const ws = getNative(sid)?.ws
      if (ws?.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: 'RESIZE', cols, rows }))
      }
    })

    term.onTitleChange((title) => {
      const sess = getSession(sid)
      if (sess) {
        const m = title.match(/^(\/.+)/)
        if (m) sess.cwd = m[1]
      }
    })

    const ro = new ResizeObserver(() => {
      try {
        fitAddon.fit()
      } catch {
        /* size 0 */
      }
    })
    ro.observe(container)
    setNativeResizeObserver(sid, ro)

    await connectSession(sid, { websshTokenPromise })

    setTimeout(() => {
      try {
        fitAddon.fit()
      } catch {
        /* size 0 */
      }
    }, 100)
    term.focus()
  }

  async function applyRouteQuery() {
    const id = parseRouteServerId()
    const path = sanitizeRemotePath(String(route.query.path || ''))
    const forceNew = route.query.new_tab === '1' || route.query.new_tab === 'true'

    if (!id) return

    if (!forceNew) {
      const existing = sessions.value.find((s) => s.serverId === id)
      if (existing) {
        await switchTab(existing.id)
        if (path) sendCdToSession(existing, path)
        return
      }
    }
    await newSession(id, { path })
  }

  function toggleFullscreen() {
    if (!document.fullscreenElement) {
      termContainer.value?.requestFullscreen?.()
    } else {
      document.exitFullscreen?.()
    }
  }

  async function loadQuickCmds() {
    try {
      const res = await http.get<QuickCmdItem[]>('/terminal/quick-commands')
      quickCmds.value = sortQuickCommands(res || [])
    } catch {
      snackbar('快捷命令加载失败，已使用内置命令', 'warning')
      quickCmds.value = sortQuickCommands([
        {
          id: 1,
          name: '系统状态',
          cmd: 'systemctl status --no-pager 2>&1 | head -20\r',
          is_builtin: true,
          sort_order: 1001,
        },
        {
          id: 2,
          name: '系统日志',
          cmd: 'journalctl -n 30 --no-pager 2>&1\r',
          is_builtin: true,
          sort_order: 1002,
        },
        {
          id: 3,
          name: '磁盘使用',
          cmd: 'df -h\r',
          is_builtin: true,
          sort_order: 1003,
        },
        {
          id: 4,
          name: '内存使用',
          cmd: 'free -h\r',
          is_builtin: true,
          sort_order: 1004,
        },
        {
          id: 5,
          name: '进程列表',
          cmd: 'ps aux --sort=-%mem 2>&1 | head -15\r',
          is_builtin: true,
          sort_order: 1005,
        },
      ])
    }
  }

  function execQuickCmd(cmd: string) {
    const s = activeSession.value
    if (!s || !isSessionReady(s)) {
      snackbar(s?.status === 'connecting' ? '终端连接中，请稍候' : '终端未连接', 'warning')
      return
    }
    sendWsData(s.id, formatQuickCmdForSend(cmd))
    getNative(s.id)?.term?.focus()
  }

  async function loadServers() {
    serversLoading.value = true
    try {
      await loadHistoryOnly()
      const { items } = await fetchPagePerPage<ServerApiItem>('/servers/', {}, 1, -1)
      servers.value = items.map(toTerminalServerItem)
    } catch {
      servers.value = []
      snackbar('加载服务器列表失败', 'warning')
    } finally {
      serversLoading.value = false
    }
  }

  function commitServerSearch() {
    void recordTerminalSearch(serverSearch.value)
  }

  function onServerSearchUpdate(val: string | null) {
    if (val == null || val === '') {
      serverSearch.value = ''
      void recordTerminalSearch('')
      return
    }
    const picked = String(val).trim()
    serverSearch.value = picked
    if (terminalSearchHistory.value.includes(picked)) {
      void recordTerminalSearch(picked)
    }
  }

  function onGlobalKeydown(e: KeyboardEvent) {
    if (e.ctrlKey && e.key === 'l') {
      e.preventDefault()
      const s = activeSession.value
      const native = s ? getNative(s.id) : undefined
      if (native?.term) {
        native.term.clear()
        if (isSessionReady(s!)) sendWsData(s!.id, '\x0c')
      }
      return
    }
    // US 键盘 Ctrl+加号 可能上报 '+' 或 '='（同一物理键），只处理一次
    if (e.ctrlKey && (e.key === '+' || e.key === '=')) {
      e.preventDefault()
      settings.changeFontSize(1)
      return
    }
    if (e.ctrlKey && e.key === '-') {
      e.preventDefault()
      settings.changeFontSize(-1)
    }
    if (e.ctrlKey && e.shiftKey && e.key === 'F') {
      e.preventDefault()
      toggleFullscreen()
    }
  }

  function disposeAllSessions() {
    const ids = sessions.value.map((s) => s.id)
    ids.forEach(disposeSessionNative)
    termRefs.clear()
    sessions.value = []
    activeSessionId.value = ''
  }

  watch(
    () => [route.query.server_id, route.query.server, route.query.path, route.query.new_tab],
    () => {
      void applyRouteQuery()
    },
  )

  return {
    sessions,
    activeSessionId,
    activeSession,
    activeSessionConnected,
    cmdBarDisabled,
    termContainer,
    showServerDialog,
    servers,
    serversLoading,
    serverSearch,
    terminalSearchHistory,
    filteredServers,
    showEmptyPicker,
    quickCmds,
    fontSize: settings.fontSize,
    scrollback: settings.scrollback,
    scrollbackOptions: settings.scrollbackOptions,
    changeFontSize: settings.changeFontSize,
    changeScrollback: settings.changeScrollback,
    cmdInput: cmdBar.cmdInput,
    sendCmd: cmdBar.sendCmd,
    onCmdKeydown: cmdBar.onCmdKeydown,
    setTermRef,
    switchTab,
    closeTab,
    newSession,
    disconnect,
    reconnect,
    toggleFullscreen,
    loadQuickCmds,
    execQuickCmd,
    loadServers,
    commitServerSearch,
    onServerSearchUpdate,
    applyRouteQuery,
    onGlobalKeydown,
    disposeAllSessions,
    getNative,
    sendWsData,
  }
}
