import { describe, expect, it } from 'vitest'
import { execFileSync } from 'node:child_process'
import {
  existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync,
  readlinkSync, symlinkSync, writeFileSync,
} from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'

const script = resolve('scripts/setup-consumer.sh')
const TVU = resolve('.')
const DS_SKILL_COUNT = readdirSync(join(TVU, 'skills')).length

function newConsumer(): string {
  return mkdtempSync(join(tmpdir(), 'tvu-consumer-'))
}

function run(cwd: string) {
  return execFileSync('bash', [script], {
    cwd,
    encoding: 'utf8',
    stdio: ['ignore', 'pipe', 'pipe'],
  })
}

function runExpectingFailure(cwd: string): { status: number, stderr: string } {
  try {
    run(cwd)
    return { status: 0, stderr: '' }
  } catch (e) {
    const err = e as { status: number, stderr: string }
    return { status: err.status, stderr: err.stderr }
  }
}

describe('setup-consumer.sh — 目录级 symlink (INFRA-F110)', () => {
  it('S-a: 全新 consumer → .claude/skills 是 symlink，解析出 DS 全部 skill', () => {
    const c = newConsumer()
    run(c)
    const link = join(c, '.claude/skills')
    expect(lstatSync(link).isSymbolicLink()).toBe(true)
    expect(readlinkSync(link)).toBe(join(TVU, 'skills'))
    expect(readdirSync(link).length).toBe(DS_SKILL_COUNT)
  })

  it('S-b: 旧式真目录（内含全 symlink）→ 迁移成 symlink，无嵌套残留', () => {
    const c = newConsumer()
    mkdirSync(join(c, '.claude/skills'), { recursive: true })
    symlinkSync(join(TVU, 'skills/role-ux'), join(c, '.claude/skills/role-ux'))
    symlinkSync(join(TVU, 'skills/tvu-design-code'), join(c, '.claude/skills/tvu-design-code'))

    run(c)

    const link = join(c, '.claude/skills')
    expect(lstatSync(link).isSymbolicLink()).toBe(true)
    expect(readdirSync(link)).not.toContain('skills')
    expect(readdirSync(link).length).toBe(DS_SKILL_COUNT)
  })

  it('S-c: 旧式真目录且链接已断 → 仍可安全迁移（断链也是 symlink）', () => {
    const c = newConsumer()
    mkdirSync(join(c, '.claude/skills'), { recursive: true })
    symlinkSync(join(TVU, '.claude/skills/design-discovery'), join(c, '.claude/skills/design-discovery'))
    expect(existsSync(join(c, '.claude/skills/design-discovery'))).toBe(false)

    run(c)

    expect(lstatSync(join(c, '.claude/skills')).isSymbolicLink()).toBe(true)
  })

  it('S7: 真目录含非 symlink 条目 → 拒绝执行，且一个文件都不删', () => {
    const c = newConsumer()
    mkdirSync(join(c, '.claude/skills/my-local-skill'), { recursive: true })
    const asset = join(c, '.claude/skills/my-local-skill/SKILL.md')
    writeFileSync(asset, '# 项目专属 skill\n')

    const { status, stderr } = runExpectingFailure(c)

    expect(status).not.toBe(0)
    expect(existsSync(asset)).toBe(true)
    expect(lstatSync(join(c, '.claude/skills')).isSymbolicLink()).toBe(false)
    expect(stderr).toContain('my-local-skill')
  })

  it('S4: 已是 symlink 时重复执行幂等', () => {
    const c = newConsumer()
    run(c)
    run(c)
    const link = join(c, '.claude/skills')
    expect(lstatSync(link).isSymbolicLink()).toBe(true)
    expect(readdirSync(link).length).toBe(DS_SKILL_COUNT)
    expect(readdirSync(link)).not.toContain('skills')
  })

  it('S3: vocabulary.md symlink 仍解析得到', () => {
    const c = newConsumer()
    run(c)
    const vocab = join(c, '.claude/vocabulary.md')
    expect(lstatSync(vocab).isSymbolicLink()).toBe(true)
    expect(existsSync(vocab)).toBe(true)
  })

  it('S5: DS 新增 skill 后 consumer 无需任何动作即可见', () => {
    const c = newConsumer()
    run(c)
    const probe = join(TVU, 'skills/__f110-probe__')
    try {
      mkdirSync(probe)
      writeFileSync(join(probe, 'SKILL.md'), '---\nname: __f110-probe__\n---\n')
      expect(readdirSync(join(c, '.claude/skills'))).toContain('__f110-probe__')
    } finally {
      execFileSync('rm', ['-rf', probe])
    }
  })
})
