import { io } from 'socket.io-client'
import { writeFile } from 'node:fs/promises'

const DEVICE_ID = process.env.RC_DEVICE_ID || process.argv[2]
const SERVER_HTTP = process.env.RC_SERVER_HTTP || 'http://127.0.0.1:3001'
const SOCKET_URL = process.env.RC_SOCKET_URL || SERVER_HTTP
const USERNAME = process.env.RC_USER || 'superadmin'
const PASSWORD = process.env.RC_PASS || 'superadmin123456789'
const RUNS = Number(process.env.RC_APP_LIST_RUNS || 4)
const OPEN_BATTERY_SETTINGS = String(process.env.RC_OPEN_BATTERY_SETTINGS || '0') === '1'
const REPORT_PATH = process.env.RC_REPORT_PATH || './_tmp_keepalive_app_list_probe.json'

if (!DEVICE_ID) {
  console.error('usage: node scripts/keepalive_app_list_probe.mjs <deviceId>')
  process.exit(1)
}

const nowIso = () => new Date().toISOString()
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

const report = {
  startedAt: nowIso(),
  serverHttp: SERVER_HTTP,
  socketUrl: SOCKET_URL,
  deviceId: DEVICE_ID,
  runs: [],
  keepalive: {},
  openBatterySettings: null,
  events: [],
  summary: { appListOk: 0, appListFail: 0 }
}

function trimPreview(value, maxLen = 360) {
  try {
    const text = JSON.stringify(value)
    if (!text) return ''
    return text.length > maxLen ? `${text.slice(0, maxLen)}...<trimmed>` : text
  } catch {
    return String(value)
  }
}

function pushEvent(event, payload) {
  report.events.push({ at: nowIso(), event, payload: trimPreview(payload) })
  if (report.events.length > 600) report.events.shift()
}

function waitEvent(socket, event, predicate, timeoutMs) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      cleanup()
      reject(new Error(`timeout_waiting_${event}_${timeoutMs}ms`))
    }, timeoutMs)

    const handler = (...args) => {
      try {
        if (!predicate(...args)) return
        cleanup()
        resolve(args)
      } catch (error) {
        cleanup()
        reject(error)
      }
    }

    const cleanup = () => {
      clearTimeout(timer)
      socket.off(event, handler)
    }

    socket.on(event, handler)
  })
}

async function loginToken() {
  const resp = await fetch(`${SERVER_HTTP}/api/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: USERNAME, password: PASSWORD })
  })
  const data = await resp.json().catch(() => ({}))
  if (!resp.ok || !data?.success || !data?.token) {
    throw new Error(`login_failed_${resp.status}_${trimPreview(data, 800)}`)
  }
  return data.token
}

async function run() {
  const token = await loginToken()
  const socket = io(SOCKET_URL, {
    transports: ['websocket', 'polling'],
    timeout: 12000,
    reconnection: false,
    auth: { token }
  })

  socket.onAny((event, ...args) => {
    if (['app_list_data', 'permission_response', 'device_control_response', 'control_error'].includes(event)) {
      pushEvent(event, args[0])
    }
  })

  const emitClientEvent = (type, data = {}) => {
    socket.emit('client_event', { type, data: { ...data, deviceId: DEVICE_ID }, timestamp: Date.now() })
  }

  const emitControl = (type, data = {}) => {
    socket.emit('control_message', { type, deviceId: DEVICE_ID, data, timestamp: Date.now() })
  }

  const emitCamera = (action, data = {}) => {
    socket.emit('camera_control', { action, deviceId: DEVICE_ID, data })
  }

  let hasControl = false

  try {
    await waitEvent(socket, 'connect', () => true, 12000)
    socket.emit('web_client_register', { userAgent: 'codex-keepalive-app-list-probe' })
    await waitEvent(socket, 'client_registered', () => true, 12000)

    emitClientEvent('REQUEST_DEVICE_CONTROL', { deviceId: DEVICE_ID })
    const [controlResp] = await waitEvent(
      socket,
      'device_control_response',
      (payload) => payload?.deviceId === DEVICE_ID,
      12000
    )
    if (!controlResp?.success) {
      throw new Error(controlResp?.message || 'request_control_failed')
    }
    hasControl = true

    for (let i = 1; i <= RUNS; i += 1) {
      const includeIcons = i % 2 === 0
      const started = Date.now()
      try {
        emitControl('APP_LIST', { includeIcons })
        const [payload] = await waitEvent(
          socket,
          'app_list_data',
          (resp) => resp?.deviceId === DEVICE_ID && Array.isArray(resp?.appList),
          includeIcons ? 35000 : 18000
        )
        const count = Array.isArray(payload?.appList) ? payload.appList.length : 0
        report.runs.push({ idx: i, includeIcons, ok: true, count, durationMs: Date.now() - started })
        report.summary.appListOk += 1
      } catch (error) {
        report.runs.push({
          idx: i,
          includeIcons,
          ok: false,
          error: error instanceof Error ? error.message : String(error),
          durationMs: Date.now() - started
        })
        report.summary.appListFail += 1
      }
      await sleep(includeIcons ? 900 : 450)
    }

    const waitBattery = waitEvent(
      socket,
      'permission_response',
      (payload) =>
        payload?.deviceId === DEVICE_ID &&
        String(payload?.permissionType || '').toLowerCase() === 'battery_optimization',
      15000
    )

    const waitBackground = waitEvent(
      socket,
      'permission_response',
      (payload) =>
        payload?.deviceId === DEVICE_ID &&
        String(payload?.permissionType || '').toLowerCase() === 'background_start',
      15000
    )

    emitCamera('KEEPALIVE_STATUS_CHECK', {})

    const [[batteryResp], [backgroundResp]] = await Promise.all([waitBattery, waitBackground])

    report.keepalive = {
      batteryOptimization: {
        success: !!batteryResp?.success,
        message: batteryResp?.message || ''
      },
      backgroundStart: {
        success: !!backgroundResp?.success,
        message: backgroundResp?.message || ''
      }
    }

    if (OPEN_BATTERY_SETTINGS) {
      const waitBatteryAfterOpen = waitEvent(
        socket,
        'permission_response',
        (payload) =>
          payload?.deviceId === DEVICE_ID &&
          String(payload?.permissionType || '').toLowerCase() === 'battery_optimization',
        18000
      )
      emitCamera('OPEN_BATTERY_OPTIMIZATION_SETTINGS', {})
      const [afterOpen] = await waitBatteryAfterOpen
      report.openBatterySettings = {
        success: !!afterOpen?.success,
        message: afterOpen?.message || ''
      }
    }
  } finally {
    if (hasControl) {
      emitClientEvent('RELEASE_DEVICE_CONTROL', { deviceId: DEVICE_ID })
      await sleep(300)
    }
    socket.close()
  }

  report.finishedAt = nowIso()
  await writeFile(REPORT_PATH, JSON.stringify(report, null, 2), 'utf8')
  console.log(`report=${REPORT_PATH}`)
  console.log(`app_list_ok=${report.summary.appListOk} app_list_fail=${report.summary.appListFail}`)
  console.log(`keepalive_battery=${report.keepalive?.batteryOptimization?.success} keepalive_bg=${report.keepalive?.backgroundStart?.success}`)
}

run().catch(async (error) => {
  report.finishedAt = nowIso()
  report.fatal = error instanceof Error ? error.message : String(error)
  await writeFile(REPORT_PATH, JSON.stringify(report, null, 2), 'utf8')
  console.error(report.fatal)
  process.exit(1)
})
