import { test } from 'node:test'
import assert from 'node:assert/strict'
import os from 'node:os'
import path from 'node:path'
import { promises as fs } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { detectHosts, parseHostsOverride } from '../../src/installer/detect.js'
import { classifyFile, sha256, readManifest, writeManifest } from '../../src/installer/manifest.js'
import { mergeClaudeSettings, SESSION_HOOK_COMMAND } from '../../src/installer/shells.js'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const REGISTRY = JSON.parse(
  await fs.readFile(path.join(__dirname, '../../adapters/registry.json'), 'utf8')
)

async function tmpDir(prefix = 'wnw-inst-') {
  const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix))
  return { root, cleanup: () => fs.rm(root, { recursive: true, force: true }) }
}

test('detectHosts：PATH 上有探测名才命中（win32 认 PATHEXT）', async () => {
  const { root, cleanup } = await tmpDir('wnw-path-')
  try {
    // 造一个假 claude 可执行
    const ext = process.platform === 'win32' ? '.cmd' : ''
    await fs.writeFile(path.join(root, `claude${ext}`), '#!/bin/sh\n', 'utf8')
    const env = { PATH: root, PATHEXT: '.COM;.EXE;.BAT;.CMD' }
    const hits = await detectHosts(REGISTRY, { env })
    assert.deepEqual(hits, ['claude-code'])
    // 空 PATH → 全不命中
    assert.deepEqual(await detectHosts(REGISTRY, { env: { PATH: '' } }), [])
  } finally {
    await cleanup()
  }
})

test('parseHostsOverride：未知宿主人话报错并列可用项', () => {
  const bad = parseHostsOverride('claude-code,不存在', REGISTRY)
  assert.equal(bad.ok, false)
  assert.ok(bad.error.includes('不存在') && bad.error.includes('codex'))
  const good = parseHostsOverride('codex, cursor', REGISTRY)
  assert.deepEqual(good.hosts, ['codex', 'cursor'])
})

test('manifest：三态判定 + 读写往返', async () => {
  const { root, cleanup } = await tmpDir()
  try {
    const m = { version: '7.0.0-alpha', files: { 'a.md': sha256('旧内容') } }
    await writeManifest(root, m)
    const back = await readManifest(root)
    assert.equal(back.files['a.md'], sha256('旧内容'))

    assert.equal(classifyFile('新.md', null, back), 'new')
    assert.equal(classifyFile('a.md', null, back), 'missing')
    assert.equal(classifyFile('a.md', sha256('旧内容'), back), 'unchanged')
    assert.equal(classifyFile('a.md', sha256('用户改过'), back), 'user-modified')
    assert.equal(classifyFile('a.md', sha256('x'), null), 'new')
  } finally {
    await cleanup()
  }
})

test('mergeClaudeSettings：新建/保留用户配置/幂等/坏 JSON 不动', () => {
  // 新建
  const a = mergeClaudeSettings(null, SESSION_HOOK_COMMAND)
  assert.equal(a.changed, true)
  const parsedA = JSON.parse(a.content)
  assert.equal(parsedA.hooks.SessionStart[0].hooks[0].command, SESSION_HOOK_COMMAND)

  // 保留用户已有 hooks 与其他配置
  const user = JSON.stringify({
    permissions: { allow: ['Bash'] },
    hooks: { SessionStart: [{ hooks: [{ type: 'command', command: 'echo 自己的' }] }] },
  })
  const b = mergeClaudeSettings(user, SESSION_HOOK_COMMAND)
  assert.equal(b.changed, true)
  const parsedB = JSON.parse(b.content)
  assert.deepEqual(parsedB.permissions.allow, ['Bash'])
  assert.equal(parsedB.hooks.SessionStart.length, 2)

  // 幂等：再合并不重复
  const c = mergeClaudeSettings(b.content, SESSION_HOOK_COMMAND)
  assert.equal(c.changed, false)
  assert.equal(JSON.parse(c.content).hooks.SessionStart.length, 2)

  // 坏 JSON → 不动原文,报 error
  const d = mergeClaudeSettings('{坏的', SESSION_HOOK_COMMAND)
  assert.equal(d.changed, false)
  assert.ok(d.error)
  assert.equal(d.content, '{坏的')
})
