import { spawn } from 'node:child_process'
import { existsSync } from 'node:fs'
import { mkdir, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { resolve } from 'node:path'
import { createServer as createViteServer } from 'vite'
import { createChromeProfile, removeChromeProfile } from './chrome-profile.mjs'

const WebSocket = createRequire(import.meta.url)('ws')

const root = resolve(new URL('..', import.meta.url).pathname)
const reportPath = resolve(root, 'config/chrome-cam-native-simulation-verification.json')
const executable = process.env.CHROME_BIN || '/home/mes123456/.local/bin/google-chrome'
if (!existsSync(executable)) throw new Error(`Chrome executable is unavailable: ${executable}`)
const vite = await createViteServer({
  root,
  server: {
    host: '127.0.0.1', port: 0, strictPort: false,
    watch: { ignored: ['**/cnc_wams_gpt6/**', '**/OpenCAMLib/**', '**/working_ocl/**', '**/.cache/**'] },
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp',
      'Cross-Origin-Resource-Policy': 'same-origin',
    },
  },
  optimizeDeps: { exclude: ['@bitbybit-dev/occt', '@bitbybit-dev/occt-worker'] },
})
await vite.listen()
const address = vite.httpServer?.address()
if (!address || typeof address === 'string') throw new Error('Native CAM harness server did not expose a port.')
const profile = await createChromeProfile('cam-native-simulation')
const chrome = spawn(executable, [
  '--headless=new', '--no-sandbox', '--disable-dev-shm-usage', '--no-first-run',
  '--no-default-browser-check', '--enable-webgl', '--enable-unsafe-swiftshader',
  '--use-angle=swiftshader', '--remote-debugging-port=0', `--user-data-dir=${profile}`, 'about:blank',
], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'] })
let stderr = ''
const debuggerUrl = await new Promise((resolveDebugger, rejectDebugger) => {
  const timer = setTimeout(() => rejectDebugger(new Error(`Chrome DevTools endpoint timed out. ${stderr.slice(-2000)}`)), 30_000)
  chrome.stderr.on('data', (chunk) => {
    stderr += String(chunk)
    const match = stderr.match(/DevTools listening on (ws:\/\/[^\s]+)/)
    if (match) { clearTimeout(timer); resolveDebugger(match[1]) }
  })
  chrome.once('exit', (code, signal) => {
    clearTimeout(timer)
    rejectDebugger(new Error(`Chrome exited before DevTools was ready: code=${code}, signal=${signal}.`))
  })
})

class CdpClient {
  constructor(url) { this.socket = new WebSocket(url); this.sequence = 0; this.pending = new Map() }
  async open() {
    await new Promise((resolveOpen, rejectOpen) => {
      this.socket.addEventListener('open', resolveOpen, { once: true })
      this.socket.addEventListener('error', () => rejectOpen(new Error('Chrome DevTools WebSocket failed to open.')), { once: true })
    })
    this.socket.addEventListener('message', (event) => {
      const message = JSON.parse(String(event.data))
      if (!message.id) return
      const pending = this.pending.get(message.id)
      if (!pending) return
      this.pending.delete(message.id)
      if (message.error) pending.reject(new Error(`${pending.method}: ${message.error.message}`))
      else pending.resolve(message.result)
    })
  }
  send(method, params = {}, sessionId) {
    const id = ++this.sequence
    return new Promise((resolveMessage, rejectMessage) => {
      this.pending.set(id, { resolve: resolveMessage, reject: rejectMessage, method })
      this.socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }))
    })
  }
  close() { this.socket.close() }
}

const client = new CdpClient(debuggerUrl)
let report
try {
  await client.open()
  const version = await client.send('Browser.getVersion')
  if (!/Chrome/i.test(version.product || '')) throw new Error(`Unexpected browser product: ${version.product || 'unknown'}`)
  const { targetInfos } = await client.send('Target.getTargets')
  const target = targetInfos.find((candidate) => candidate.type === 'page')
  if (!target) throw new Error('Chrome did not expose a page target.')
  const { sessionId } = await client.send('Target.attachToTarget', { targetId: target.targetId, flatten: true })
  await Promise.all([client.send('Page.enable', {}, sessionId), client.send('Runtime.enable', {}, sessionId)])
  const evaluate = async (expression) => {
    const result = await client.send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true }, sessionId)
    if (result.exceptionDetails) throw new Error(result.exceptionDetails.exception?.description || result.exceptionDetails.text || 'Chrome evaluation failed.')
    return result.result?.value
  }
  await client.send('Page.navigate', { url: `http://127.0.0.1:${address.port}/chrome-cam-native-simulation-harness.html` }, sessionId)
  const deadline = Date.now() + 120_000
  while (Date.now() < deadline) {
    report = await evaluate('window.__bitbybitCamNativeSimulationReport || null')
    if (report) break
    await new Promise((resolveWait) => setTimeout(resolveWait, 250))
  }
  if (!report) throw new Error('Chrome native CAM simulation harness timed out.')
  report = { ...report, browser: { product: version.product, userAgent: version.userAgent } }
  await mkdir(resolve(root, 'config'), { recursive: true })
  await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`)
  console.log(JSON.stringify(report, null, 2))
  if (report.status !== 'pass') process.exitCode = 1
} catch (error) {
  report = { schemaVersion: 1, status: 'failed', browserId: 'chrome', error: error instanceof Error ? error.stack || error.message : String(error) }
  await mkdir(resolve(root, 'config'), { recursive: true })
  await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`)
  console.error(JSON.stringify(report, null, 2))
  process.exitCode = 1
} finally {
  client.close()
  chrome.kill('SIGTERM')
  await vite.close()
  await removeChromeProfile(profile)
}
