import { ref, computed, watch, nextTick, type Ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTheme } from 'vuetify'
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 { sortQuickCommands, type QuickCmdItem } from '@/composables/useTerminalQuickCommands'
import { buildWebSocketUrl } from '@/utils/wsUrl'
import {
  createNative,
  deleteNative,
  getNative,
  setNativeFitAddon,
  setNativeResizeObserver,
  setNativeTerm,
  isSessionWsOpen,
  forEachNative,
} from './termNativeStore'
import {
  sanitizeRemotePath,
  type TermSessionData,
  type TerminalServerItem,
} from './types'
import { useTerminalSettings } from './useTerminalSettings'
import { useTerminalCmdBar } from './useTerminalCmdBar'

const MAX_TABS = 10

export function useTerminalSessions(nexusDrawer: Ref<boolean>) {
  const route = useRoute()
  const router = useRouter()
  const theme = useTheme()
  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 serverSearch = ref('')
  const quickCmds = ref<QuickCmdItem[]>([])

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

  const activeSessionConnected = computed(() => {
    const id = activeSessionId.value
    return id ? isSessionWsOpen(id) : false
  })

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

  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)

  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 {
    const isDark = theme.global.current.value.dark
    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: isDark
        ? {
            background: '#0b1120',
            foreground: '#e2e8f0',
            cursor: '#7c8bf4',
            selectionBackground: '#334155',
            black: '#1e293b',
            red: '#f87171',
            green: '#4ade80',
            yellow: '#fbbf24',
            blue: '#60a5fa',
            magenta: '#c084fc',
            cyan: '#22d3ee',
            white: '#e2e8f0',
            brightBlack: '#64748b',
            brightRed: '#fca5a5',
            brightGreen: '#86efac',
            brightYellow: '#fde047',
            brightBlue: '#93c5fd',
            brightMagenta: '#d8b4fe',
            brightCyan: '#67e8f9',
            brightWhite: '#f8fafc',
          }
        : {
            background: '#f8fafc',
            foreground: '#0f172a',
            cursor: '#4f46e5',
            selectionBackground: '#bfdbfe',
            black: '#1e293b',
            red: '#dc2626',
            green: '#16a34a',
            yellow: '#ca8a04',
            blue: '#2563eb',
            magenta: '#9333ea',
            cyan: '#0891b2',
            white: '#f1f5f9',
            brightBlack: '#64748b',
            brightRed: '#ef4444',
            brightGreen: '#22c55e',
            brightYellow: '#eab308',
            brightBlue: '#3b82f6',
            brightMagenta: '#a855f7',
            brightCyan: '#06b6d4',
            brightWhite: '#ffffff',
          },
      allowProposedApi: true,
      drawBoldTextInBrightColors: true,
    })
  }

  function sendCdToSession(s: TermSessionData, path: string) {
    const dir = sanitizeRemotePath(path)
    if (!dir || !isSessionWsOpen(s.id)) 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)
  }

  function disposeSessionNative(sessionId: string) {
    const native = getNative(sessionId)
    if (!native) return
    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) {
    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 = 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 = 'connected'
      s.manualDisconnect = false
      s.reconnectAttempt = 0
      if (native.reconnectTimer) {
        clearTimeout(native.reconnectTimer)
        native.reconnectTimer = null
      }
      s.showOverlay = false
      s.overlayMsg = ''
      s.reconnectCountdown = ''
      startUptimeCounter(s)
      startPing(s)
      native.term?.focus()
    }

    ws.onmessage = (e) => {
      try {
        const msg = JSON.parse(e.data as string)
        switch (msg.type) {
          case 'TERMINAL_INIT':
            s.serverName = msg.server_name || s.serverName
            {
              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 && ws.readyState === WebSocket.OPEN) {
              sendCdToSession(s, s.pendingPath)
            }
            break
          case 'DATA':
            native.term?.write(msg.data)
            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) => {
      stopPing(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.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 server = servers.value.find((s) => s.id === serverId)
    const name = server ? 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: 'idle',
      showOverlay: false,
      overlayMsg: '',
      reconnectCountdown: '',
      uptime: '',
      pingMs: null,
      connectionStartTime: 0,
      pendingPath: sanitizeRemotePath(opts?.path || ''),
      initialCdSent: false,
    }

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

    await switchTab(sid)
    await nextTick()

    const container = termRefs.get(sid)
    if (!container) 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) => {
      if (isSessionWsOpen(sid)) 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)

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

  async function applyRouteQuery() {
    const id = Number(route.query.server_id) || 0
    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 || !isSessionWsOpen(s.id)) {
      snackbar('终端未连接', 'warning')
      return
    }
    sendWsData(s.id, cmd)
    getNative(s.id)?.term?.focus()
  }

  async function loadServers() {
    try {
      const res = await http.get<{ items: TerminalServerItem[] }>('/servers/', {
        per_page: 200,
      })
      servers.value = res.items || []
    } catch {
      /* non-critical */
    }
  }

  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 (isSessionWsOpen(s!.id)) 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.path, route.query.new_tab],
    () => {
      void applyRouteQuery()
    },
  )

  return {
    sessions,
    activeSessionId,
    activeSession,
    activeSessionConnected,
    termContainer,
    showServerDialog,
    servers,
    serverSearch,
    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,
    sendCtrl: cmdBar.sendCtrl,
    sendKey: cmdBar.sendKey,
    setTermRef,
    switchTab,
    closeTab,
    newSession,
    disconnect,
    reconnect,
    toggleFullscreen,
    loadQuickCmds,
    execQuickCmd,
    loadServers,
    applyRouteQuery,
    onGlobalKeydown,
    disposeAllSessions,
    getNative,
    sendWsData,
  }
}
