/**
 * 本地后端端到端联调：通知 / SSE / 互联
 * 用法: node scripts/e2e-check.mjs [baseUrl] [user] [pass]
 */
import fs from 'node:fs'
import path from 'node:path'

const base = process.argv[2] || 'http://127.0.0.1:8052'
const username = process.argv[3] || 'admin'
const password = process.argv[4] || 'admin123'
const cookieJar = path.join(process.cwd(), 'cookies-e2e.txt')

const results = []

function log(name, ok, detail = '') {
  results.push({ name, ok, detail })
  const mark = ok ? 'PASS' : 'FAIL'
  console.log(`[${mark}] ${name}${detail ? ' - ' + detail : ''}`)
}

function parseSetCookie(res) {
  // undici/node fetch: getSetCookie if available
  const list =
    typeof res.headers.getSetCookie === 'function'
      ? res.headers.getSetCookie()
      : (() => {
          const single = res.headers.get('set-cookie')
          return single ? [single] : []
        })()
  const map = new Map()
  if (fs.existsSync(cookieJar)) {
    for (const line of fs.readFileSync(cookieJar, 'utf8').split(/\r?\n/)) {
      if (!line || line.startsWith('#')) continue
      const parts = line.split('\t')
      if (parts.length >= 7) map.set(parts[5], parts[6])
    }
  }
  for (const c of list) {
    const [pair] = c.split(';')
    const eq = pair.indexOf('=')
    if (eq > 0) map.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim())
  }
  const lines = ['# Netscape HTTP Cookie File', '']
  for (const [k, v] of map) {
    lines.push(`127.0.0.1\tFALSE\t/\tFALSE\t0\t${k}\t${v}`)
  }
  fs.writeFileSync(cookieJar, lines.join('\n'))
  return [...map.entries()].map(([k, v]) => `${k}=${v}`).join('; ')
}

function loadCookieHeader() {
  if (!fs.existsSync(cookieJar)) return ''
  const pairs = []
  for (const line of fs.readFileSync(cookieJar, 'utf8').split(/\r?\n/)) {
    if (!line || line.startsWith('#')) continue
    const parts = line.split('\t')
    if (parts.length >= 7) pairs.push(`${parts[5]}=${parts[6]}`)
  }
  return pairs.join('; ')
}

async function api(method, p, body) {
  const headers = { Accept: 'application/json' }
  const cookie = loadCookieHeader()
  if (cookie) headers.Cookie = cookie
  if (body !== undefined) headers['Content-Type'] = 'application/json'
  const res = await fetch(`${base}${p}`, {
    method,
    headers,
    body: body !== undefined ? JSON.stringify(body) : undefined,
  })
  parseSetCookie(res)
  const text = await res.text()
  let json
  try {
    json = JSON.parse(text)
  } catch {
    json = { raw: text }
  }
  return { status: res.status, json, headers: res.headers }
}

async function readSSE(p, ms = 2000) {
  const cookie = loadCookieHeader()
  const ctrl = new AbortController()
  const timer = setTimeout(() => ctrl.abort(), ms)
  try {
    const res = await fetch(`${base}${p}`, {
      headers: { Accept: 'text/event-stream', Cookie: cookie },
      signal: ctrl.signal,
    })
    const reader = res.body?.getReader()
    if (!reader) return { status: res.status, text: '' }
    const dec = new TextDecoder()
    let text = ''
    while (true) {
      const { done, value } = await reader.read()
      if (done) break
      text += dec.decode(value, { stream: true })
      if (text.includes('data:')) break
    }
    reader.cancel().catch(() => {})
    return { status: res.status, text }
  } catch (e) {
    return { status: 0, text: String(e?.message || e) }
  } finally {
    clearTimeout(timer)
  }
}

async function main() {
  console.log(`E2E base=${base}`)

  // login
  const login = await api('POST', '/api/v1/auth/login', { username, password })
  log('auth.login', login.status === 200 && login.json?.code === 200, JSON.stringify(login.json?.msg || login.json))

  const me = await api('GET', '/api/v1/auth/me')
  log('auth.me', me.status === 200 && me.json?.code === 200, JSON.stringify(me.json?.data || me.json?.msg))

  // notify
  const types = await api('GET', '/api/v1/notify/types')
  const typeList = types.json?.data?.channel_types || []
  log('notify.types', types.status === 200 && typeList.length > 0, `count=${typeList.length}`)

  const channelBody = {
    name: 'e2e-custom',
    type: 'Custom',
    enabled: true,
    config: {
      webhook: 'http://127.0.0.1:9/hook',
      body: '{"text":"TEXT"}',
    },
  }
  const saveCh = await api('POST', '/api/v1/notify/channels', channelBody)
  log('notify.saveChannel', saveCh.status === 200 && saveCh.json?.code === 200, JSON.stringify(saveCh.json?.msg || saveCh.json))

  const channels = await api('GET', '/api/v1/notify/channels')
  const chList = Array.isArray(channels.json?.data) ? channels.json.data : []
  log('notify.listChannels', channels.status === 200 && channels.json?.code === 200, `count=${chList.length}`)

  const testCh = await api('POST', '/api/v1/notify/channels/test', channelBody)
  // 预期连接失败但接口应返回 200 + result 结构
  log(
    'notify.testChannel',
    testCh.status === 200 && testCh.json?.code === 200,
    JSON.stringify(testCh.json?.data || testCh.json?.msg),
  )

  const settingsNotify = await api('GET', '/api/v1/settings/notify')
  const token = settingsNotify.json?.data?.token || settingsNotify.json?.data?.notify_token || ''
  log('settings.notify', settingsNotify.status === 200 && settingsNotify.json?.code === 200, token ? `token=${token.slice(0, 8)}...` : 'no token')

  // interconnect
  const nodeBody = {
    name: `e2e-node-${Date.now()}`,
    url: 'http://127.0.0.1:18052',
    token: 'test-token',
    remark: 'e2e',
  }
  const createNode = await api('POST', '/api/v1/interconnect/nodes', nodeBody)
  const nodeId = createNode.json?.data?.id
  log('interconnect.createNode', createNode.status === 200 && !!nodeId, nodeId || JSON.stringify(createNode.json))

  const nodes = await api('GET', '/api/v1/interconnect/nodes')
  const nodeList = Array.isArray(nodes.json?.data) ? nodes.json.data : []
  log('interconnect.listNodes', nodes.status === 200 && nodeList.length > 0, `count=${nodeList.length}`)

  const child = await api('GET', '/api/v1/interconnect/child/status')
  log('interconnect.childStatus', child.status === 200 && child.json?.code === 200, JSON.stringify(child.json?.data))

  if (nodeId) {
    const st = await api('GET', `/api/v1/interconnect/nodes/${nodeId}/status`)
    log('interconnect.nodeStatus', st.status === 200, JSON.stringify(st.json?.data || st.json?.msg))
  }

  // monitor SSE
  const mon = await readSSE('/api/v1/monitor/sse', 2500)
  log('monitor.sse', mon.text.includes('data:') || mon.text.includes('cpu') || mon.text.includes('host'), mon.text.slice(0, 120).replace(/\s+/g, ' '))

  // logs SSE param validation
  const bad = await api('GET', '/api/v1/logs/sse?id=test')
  log('logs.sse.paramGuard', bad.status === 400 || bad.json?.error || bad.json?.msg, JSON.stringify(bad.json))

  // stats
  const stats = await api('GET', '/api/v1/stats')
  log('dashboard.stats', stats.status === 200 && stats.json?.code === 200, JSON.stringify(stats.json?.data))

  const failed = results.filter((r) => !r.ok)
  console.log('\n==== summary ====')
  console.log(`total=${results.length} pass=${results.length - failed.length} fail=${failed.length}`)
  if (failed.length) {
    process.exitCode = 1
  }
}

main().catch((e) => {
  console.error(e)
  process.exit(1)
})
