/**
 * B4/H4 wiring test — closes the frpc-log capture loop that `HealthReport.healthy` depends on.
 *
 * Regression guarded: nothing used to WRITE `<stateDir>/frpc.log`, so `readFrpcLog` always returned
 * '' and `frpcProxyStarted` was permanently false — health could never be true. This exercises the
 * real production path end-to-end: `createFileLoggingSpawn` (the spawn `superviseNative` injects)
 * tees a child's stdout into the exact file `readFrpcLog` scans, and `frpcProxyStarted` detects the
 * "start proxy success" line. Writer path and reader path share `frpcLogPath`, so they can't diverge.
 */
import { afterEach, describe, expect, it } from 'vitest'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { createFileLoggingSpawn, type FrpcChild } from '../src/transport/frpSupervise.js'
import { frpcLogPath, readFrpcLog } from '../src/cli/deps.js'
import { frpcProxyStarted } from '../src/health/probe.js'

const SUCCESS_LINE = '[web-terminal] start proxy success'

/** Poll `predicate` until true or `timeoutMs` elapses (real IO flush is async). */
async function waitFor(predicate: () => boolean, timeoutMs = 5000): Promise<boolean> {
  const deadline = Date.now() + timeoutMs
  while (Date.now() < deadline) {
    if (predicate()) return true
    await new Promise((r) => setTimeout(r, 25))
  }
  return predicate()
}

/** Write an executable fake `frpc` (node shebang) that repeatedly prints the success line to stdout. */
function writeFakeFrpc(dir: string): string {
  const bin = join(dir, 'fake-frpc.mjs')
  const script =
    `#!${process.execPath}\n` +
    `process.stdout.write(${JSON.stringify(`${SUCCESS_LINE}\n`)})\n` +
    `setInterval(() => process.stdout.write(${JSON.stringify(`${SUCCESS_LINE}\n`)}), 100)\n`
  writeFileSync(bin, script, { mode: 0o755 })
  return bin
}

describe('B4/H4 frpc-log capture wiring (createFileLoggingSpawn ↔ readFrpcLog)', () => {
  const dirs: string[] = []
  let child: FrpcChild | null = null

  afterEach(() => {
    child?.kill()
    child = null
    for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true })
  })

  it('tees the frpc child stdout into the file readFrpcLog scans → proxyStarted becomes true', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'frpclog-'))
    dirs.push(dir)

    // Before any child runs, the log is empty and the proxy is not started.
    expect(readFrpcLog(dir)).toBe('')
    expect(frpcProxyStarted(readFrpcLog(dir))).toBe(false)

    // Spawn via the SAME factory superviseNative injects, pointed at the SAME path it reads.
    const bin = writeFakeFrpc(dir)
    const spawn = createFileLoggingSpawn(frpcLogPath(dir))
    child = spawn(bin, join(dir, 'frpc.toml'))

    const detected = await waitFor(() => frpcProxyStarted(readFrpcLog(dir)))
    expect(detected).toBe(true)
    expect(readFrpcLog(dir)).toContain('start proxy success')
  })

  it('createFileLoggingSpawn truncates a stale log so a dead child’s success line is not reused', async () => {
    const dir = mkdtempSync(join(tmpdir(), 'frpclog-'))
    dirs.push(dir)

    // Simulate a leftover log from a previous (now dead) frpc that had succeeded.
    writeFileSync(frpcLogPath(dir), `${SUCCESS_LINE}\n`)
    expect(frpcProxyStarted(readFrpcLog(dir))).toBe(true)

    // A fresh spawn whose child never prints the line must NOT keep reporting the stale success.
    const bin = join(dir, 'silent-frpc.mjs')
    writeFileSync(bin, `#!${process.execPath}\nsetInterval(() => {}, 100)\n`, { mode: 0o755 })
    const spawn = createFileLoggingSpawn(frpcLogPath(dir))
    child = spawn(bin, join(dir, 'frpc.toml'))

    // Truncate-on-spawn clears the stale line; the silent child adds none.
    const cleared = await waitFor(() => readFrpcLog(dir) === '')
    expect(cleared).toBe(true)
    expect(frpcProxyStarted(readFrpcLog(dir))).toBe(false)
  })
})

describe('frpcLogPath / readFrpcLog (deterministic)', () => {
  it('frpcLogPath joins frpc.log under the state dir', () => {
    expect(frpcLogPath('/state/x')).toBe(join('/state/x', 'frpc.log'))
  })

  it('readFrpcLog returns "" when the log file does not exist', () => {
    const dir = mkdtempSync(join(tmpdir(), 'frpclog-'))
    try {
      expect(readFrpcLog(dir)).toBe('')
    } finally {
      rmSync(dir, { recursive: true, force: true })
    }
  })
})
