/**
 * smoke-tui-pty.test.ts — SAEEOL TUI 실제 PTY 검증
 *
 * node-pty로 TUI를 띄우고:
 * 1. 서버 연결 + 초기 렌더링 확인
 * 2. 키 입력(q) → 종료 확인
 * 3. 화면에 에러 메시지 없는지 확인
 * 4. 서버 healthcheck 통과 확인
 */

import { describe, expect, test, beforeAll, afterAll } from "bun:test"
import { spawn as PtySpawn, type IPty } from "bun-pty"
import { tmpdir } from "../fixture/fixture"
import * as Log from "@saeeol/core/util/log"
import { Flag } from "@saeeol/core/flag/flag"
import path from "path"
import { disposeAllInstances } from "../fixture/fixture"

void Log.init({ print: false })

const BIN = path.resolve(import.meta.dir, "../../bin/saeeol.cjs")

// PTY 출력에서 ANSI 이스케이프 시퀀스 제거
function stripAnsi(s: string): string {
  return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "")
    .replace(/\x1b\].*?\x07/g, "")
    .replace(/\x1b\[.*?m/g, "")
    .replace(/[\x00-\x09\x0b\x0c\x0e-\x1f]/g, "")
}

describe("TUI smoke: PTY", () => {
  let pty: IPty | undefined
  let output = ""
  let tmp: Awaited<ReturnType<typeof tmpdir>>

  beforeAll(async () => {
    tmp = await tmpdir({ git: true, config: {} })
    Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = false
  })

  afterAll(async () => {
    try { pty?.kill() } catch {}
    await disposeAllInstances()
    Flag.SAEEOL_EXPERIMENTAL_HTTPAPI = true
  })

  test("TUI launches, renders, and quits on 'q'", async () => {
    // PTY에서 TUI 실행
    pty = PtySpawn("node", [BIN, "tui"], {
      name: "xterm-256color",
      cols: 120,
      rows: 30,
      cwd: tmp.path,
      env: {
        ...process.env,
        TERM: "xterm-256color",
        FORCE_COLOR: "1",
        // LLM 없이도 TUI는 떠야 함
        SAEEOL_LLM_PROVIDER: "custom",
        SAEEOL_LLM_BASE_URL: "http://localhost:1",
        SAEEOL_LLM_API_KEY: "test",
        SAEEOL_LLM_MODEL: "test",
      },
    })

    // 출력 수집
    pty.onData((data) => {
      output += data
    })

    // 5초 대기 (서버 시작 + 초기 렌더링)
    await Bun.sleep(5000)

    // 디버그: raw 출력을 파일에 저장
    const debugPath = path.join(import.meta.dir, ".tui-debug-output.txt")
    await Bun.write(debugPath, output)

    const plain = stripAnsi(output)

    // 디버그: plain 출력도 저장
    const plainPath = path.join(import.meta.dir, ".tui-debug-plain.txt")
    await Bun.write(plainPath, plain)

    // 1. 에러 메시지 없는지 확인
    const errorLines = plain.split("\n").filter((l) =>
      /unhandled|TypeError|ReferenceError|SyntaxError|Cannot find module|Error:/i.test(l.trim())
    )
    expect(errorLines).toHaveLength(0)

    // 2. TUI가 alternate screen mode로 진입했는지 확인
    // (?9001h = SGR-pxmouse, ?1004h = focus reporting, ?25l = cursor hide)
    const enteredScreenMode = output.includes("\x1b[?9001h") || output.includes("\x1b[?1004h")
    expect(enteredScreenMode).toBe(true)

    // 3. raw 출력이 최소한 ANSI 이스케이프를 포함하는지
    expect(output.length).toBeGreaterThan(10)

    // 3. 'q' 키로 종료
    pty.write("q")
    await Bun.sleep(2000)

    // 4. 프로세스가 종료되었는지
    // PTY는 kill 후 exit 이벤트가 옴
    const exited = await Promise.race([
      new Promise<boolean>((resolve) => {
        pty!.onExit(() => resolve(true))
        // 이미 종료되었을 수도
        setTimeout(() => resolve(false), 3000)
      }),
    ])

    // 종료 안 됐으면 강제 kill
    if (!exited) {
      try { pty.kill() } catch {}
    }
  })

  test("TUI enters alternate screen and responds to quit", async () => {
    const raw = output
    // alternate screen 진입 + ANSI 출력 존재 = 정상 렌더링
    expect(raw.length).toBeGreaterThan(5)
  })
})
