import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { strToU8, zipSync } from 'fflate'
import {
  afterEach,
  describe,
  expect,
  it,
  vi
} from 'vitest'
import {
  CapabilityService,
  type CapabilityCipher,
  type CapabilityServiceOptions
} from './capability-service'
import {
  BrowserProfileService,
  MemoryBrowserProfileStore
} from './browser-profile-service'
import { CapabilityDiagnostics } from './capability-diagnostics'

const temporaryDirectories: string[] = []

const cipher: CapabilityCipher = {
  isAvailable: () => true,
  encrypt: (value) => Buffer.from(`encrypted:${value}`),
  decrypt: (value) => value.toString().replace(/^encrypted:/u, '')
}

class FailingBrowserProfileService extends BrowserProfileService {
  failNextAddAfterSave = false
  failNextRemoveAfterSave = false

  override async addReference(
    ...args: Parameters<BrowserProfileService['addReference']>
  ): ReturnType<BrowserProfileService['addReference']> {
    const result = await super.addReference(...args)
    if (this.failNextAddAfterSave) {
      this.failNextAddAfterSave = false
      throw new Error('Injected add reference failure')
    }
    return result
  }

  override async removeReference(
    ...args: Parameters<BrowserProfileService['removeReference']>
  ): ReturnType<BrowserProfileService['removeReference']> {
    const result = await super.removeReference(...args)
    if (this.failNextRemoveAfterSave) {
      this.failNextRemoveAfterSave = false
      throw new Error('Injected remove reference failure')
    }
    return result
  }
}

async function writeSkill(
  root: string,
  id: string,
  name: string,
  body = '仅用于离线测试。'
): Promise<void> {
  const directory = join(root, id)
  await mkdir(directory, { recursive: true })
  await writeFile(
    join(directory, 'SKILL.md'),
    [
      '---',
      `id: ${id}`,
      `name: ${name}`,
      `description: ${name}的测试说明`,
      'version: 1.0.0',
      'tags:',
      '  - 测试',
      '---',
      '',
      `# ${name}`,
      '',
      body
    ].join('\n'),
    'utf8'
  )
}

async function createService(
  options: CapabilityServiceOptions = {
    platform: 'win32',
    architecture: 'x64',
    electronTarget: true,
    browserProfiles: new BrowserProfileService(
      new MemoryBrowserProfileStore()
    )
  }
): Promise<{
  directory: string
  filePath: string
  builtinRoot: string
  importedRoot: string
  service: CapabilityService
}> {
  const directory = await mkdtemp(join(tmpdir(), 'goodbuddy-capabilities-'))
  temporaryDirectories.push(directory)
  const filePath = join(directory, 'capabilities.json')
  const builtinRoot = join(directory, 'builtin')
  const importedRoot = join(directory, 'imported')
  await writeSkill(builtinRoot, 'document-writing', '文档写作')
  return {
    directory,
    filePath,
    builtinRoot,
    importedRoot,
    service: new CapabilityService(
      filePath,
      builtinRoot,
      importedRoot,
      cipher,
      options
    )
  }
}

afterEach(async () => {
  delete process.env.GOODBUDDY_CAPABILITY_SERVICE_SECRET
  await Promise.all(
    temporaryDirectories.splice(0).map((directory) =>
      rm(directory, { recursive: true, force: true })
    )
  )
})

describe('CapabilityService', () => {
  it('memoizes concurrent loads and retries after a failed load', async () => {
    const initialStore = {
      load: vi.fn(async () => undefined),
      save: vi.fn(async () => undefined)
    }
    const created = await createService({
      platform: 'win32',
      architecture: 'x64',
      electronTarget: true,
      browserProfiles: new BrowserProfileService(initialStore)
    })

    await Promise.all([
      created.service.getComputerCapabilityStatus('host-browser-control'),
      created.service.getComputerCapabilityStatus('host-browser-control'),
      created.service.getComputerCapabilityStatus('host-browser-control')
    ])
    expect(initialStore.load).toHaveBeenCalledOnce()

    let profileLoads = 0
    const browserProfiles = new BrowserProfileService({
      load: vi.fn(async () => {
        profileLoads += 1
        if (profileLoads === 1) {
          throw new Error('Injected profile load failure')
        }
        return undefined
      }),
      save: vi.fn(async () => undefined)
    })
    const retrying = new CapabilityService(
      created.filePath,
      created.builtinRoot,
      created.importedRoot,
      cipher,
      {
        platform: 'win32',
        architecture: 'x64',
        electronTarget: true,
        browserProfiles
      }
    )

    await expect(
      Promise.all([
        retrying.getComputerCapabilityStatus('host-browser-control'),
        retrying.getComputerCapabilityStatus('host-browser-control')
      ])
    ).rejects.toThrow('Injected profile load failure')
    await expect(
      retrying.getComputerCapabilityStatus('host-browser-control')
    ).resolves.toEqual({ enabled: false, supported: true })
    expect(profileLoads).toBe(2)
  })

  it('reports safe enabled and supported capability status', async () => {
    const { service } = await createService()

    await expect(
      service.getComputerCapabilityStatus('host-browser-control')
    ).resolves.toEqual({ enabled: false, supported: true })

    await service.setComputerCapabilityEnabled(
      'host-browser-control',
      true
    )
    await expect(
      service.getComputerCapabilityStatus('host-browser-control')
    ).resolves.toEqual({ enabled: true, supported: true })
  })

  it('discovers built-in skills and persists enablement and assignments', async () => {
    const { filePath, builtinRoot, importedRoot, service } =
      await createService()

    await expect(service.getSnapshot()).resolves.toMatchObject({
      skills: [
        {
          id: 'document-writing',
          source: 'builtin',
          enabled: true,
          assignments: ['model', 'opencode', 'continue']
        }
      ]
    })

    await service.setSkillEnabled('document-writing', false)
    await service.setSkillAssignments('document-writing', ['model'])

    const reloaded = new CapabilityService(
      filePath,
      builtinRoot,
      importedRoot,
      cipher
    )
    await expect(reloaded.getSnapshot()).resolves.toMatchObject({
      skills: [
        {
          id: 'document-writing',
          enabled: false,
          assignments: ['model']
        }
      ]
    })
    await expect(
      reloaded.getSkillInstructions('continue', 10_000)
    ).resolves.toBe('')
    await reloaded.setSkillEnabled('document-writing', true)
    await expect(
      reloaded.getSkillInstructions('model', 10_000)
    ).resolves.toContain('仅用于离线测试')
    await expect(
      reloaded.getRuntimeSkillContext('model', 10_000)
    ).resolves.toMatchObject({
      instructions: expect.stringContaining('仅用于离线测试'),
      packages: [
        {
          id: 'document-writing',
          directory: join(builtinRoot, 'document-writing')
        }
      ]
    })
  })

  it('imports and removes a managed SKILL.md package', async () => {
    const { directory, service } = await createService()
    const packageRoot = join(directory, 'source-skill')
    await writeSkill(packageRoot, 'meeting-helper', '会议助手')
    const source = join(packageRoot, 'meeting-helper')
    await writeFile(join(source, 'template.txt'), 'template', 'utf8')

    const imported = await service.importSkill(source)
    expect(imported.skills).toContainEqual(
      expect.objectContaining({
        id: 'meeting-helper',
        source: 'imported'
      })
    )

    const removed = await service.removeSkill('meeting-helper')
    expect(removed.skills).not.toContainEqual(
      expect.objectContaining({ id: 'meeting-helper' })
    )
    await expect(
      service.removeSkill('document-writing')
    ).rejects.toThrow('只能删除已导入')
  })

  it('imports a standard SKILL.md that identifies itself by name', async () => {
    const { directory, service } = await createService()
    const source = join(directory, 'standard-source', 'summarize-diff')
    await mkdir(source, { recursive: true })
    await writeFile(
      join(source, 'SKILL.md'),
      [
        '---',
        'name: summarize-diff',
        'description: |',
        '  概括暂存的改动。',
        '  当用户需要待提交变更摘要时使用。',
        'allowed-tools:',
        '  - Read',
        '  - Grep',
        'compatibility: droid',
        '---',
        '',
        '# Summarize Diff',
        '',
        '仅用于离线测试。'
      ].join('\n'),
      'utf8'
    )

    const imported = await service.importSkill(source)

    expect(imported.skills).toContainEqual(
      expect.objectContaining({
        id: 'summarize-diff',
        source: 'imported',
        description: '概括暂存的改动。 当用户需要待提交变更摘要时使用。'
      })
    )
  })

  it('imports every Skill found under a suite directory', async () => {
    const { directory, service } = await createService()
    const suite = join(directory, 'suite', 'skills')
    await writeSkill(suite, 'alpha-skill', 'Alpha')
    await writeSkill(suite, 'beta-skill', 'Beta')

    const imported = await service.importSkill(join(directory, 'suite'))

    expect(imported.skills.map((skill) => skill.id)).toEqual(
      expect.arrayContaining(['alpha-skill', 'beta-skill'])
    )
  })

  it('reports a readable error when the selected directory has no SKILL.md', async () => {
    const { directory, service } = await createService()
    const empty = join(directory, 'empty-directory')
    await mkdir(empty, { recursive: true })

    await expect(service.importSkill(empty)).rejects.toThrow(
      '没有找到 SKILL.md'
    )
  })

  it('exposes the skill directory and names skills dropped by the budget', async () => {
    const { builtinRoot, service } = await createService()
    await writeSkill(
      builtinRoot,
      'oversized-skill',
      '超长技能',
      '超长技能说明。'.repeat(80)
    )

    const instructions = await service.getSkillInstructions('model')
    expect(instructions).toContain(join(builtinRoot, 'document-writing'))
    expect(instructions).toContain(join(builtinRoot, 'oversized-skill'))

    const truncated = await service.getSkillInstructions('model', 200)
    expect(truncated).toContain('因超出注入上限未加载')
    expect(truncated).toContain('超长技能')

    const fullyTruncated = await service.getSkillInstructions('model', 1)
    expect(fullyTruncated).toContain('因超出注入上限未加载')
    expect(fullyTruncated).toContain('文档写作')
    expect(fullyTruncated).toContain('超长技能')
  })

  it('omits Skill names that exceed the OpenCode native limit', async () => {
    const { builtinRoot, service } = await createService()
    const longId = `a${'-a'.repeat(32)}`
    await writeSkill(builtinRoot, longId, '超长名称技能')

    const openCodeContext =
      await service.getRuntimeSkillContext('opencode')
    expect(openCodeContext.instructions).toContain(
      '超过 OpenCode 的 64 字符上限'
    )
    expect(openCodeContext.instructions).toContain('超长名称技能')
    expect(openCodeContext.packages).not.toContainEqual(
      expect.objectContaining({ id: longId })
    )

    const modelContext = await service.getRuntimeSkillContext('model')
    expect(modelContext.packages).toContainEqual(
      expect.objectContaining({ id: longId })
    )
  })

  it('imports a managed Skill from a ZIP package', async () => {
    const { directory, importedRoot, service } = await createService()
    const packageRoot = join(directory, 'zip-source')
    await writeSkill(packageRoot, 'meeting-helper', '会议助手')
    const skillMarkdown = await readFile(
      join(packageRoot, 'meeting-helper', 'SKILL.md')
    )
    const archivePath = join(directory, 'meeting-helper.zip')
    await writeFile(
      archivePath,
      zipSync({
        'meeting-helper/SKILL.md': skillMarkdown,
        'meeting-helper/template.txt': strToU8('template')
      })
    )

    const imported = await service.importSkill(archivePath)

    expect(imported.skills).toContainEqual(
      expect.objectContaining({
        id: 'meeting-helper',
        source: 'imported'
      })
    )
    await expect(
      readFile(
        join(importedRoot, 'meeting-helper', 'template.txt'),
        'utf8'
      )
    ).resolves.toBe('template')
  })

  it('rejects unsafe paths in a Skill ZIP package', async () => {
    const { directory, importedRoot, service } = await createService()
    const packageRoot = join(directory, 'unsafe-source')
    await writeSkill(packageRoot, 'unsafe-skill', '不安全 Skill')
    const skillMarkdown = await readFile(
      join(packageRoot, 'unsafe-skill', 'SKILL.md')
    )
    const archivePath = join(directory, 'unsafe-skill.zip')
    await writeFile(
      archivePath,
      zipSync({
        '../escape.txt': strToU8('escape'),
        'unsafe-skill/SKILL.md': skillMarkdown
      })
    )

    await expect(service.importSkill(archivePath)).rejects.toThrow(
      'Skill ZIP 包含不安全路径'
    )
    await expect(
      readFile(join(importedRoot, 'escape.txt'), 'utf8')
    ).rejects.toThrow()
  })

  it('encrypts remote MCP secrets and never returns them publicly', async () => {
    const { filePath, service } = await createService()
    const snapshot = await service.saveMcpServer(undefined, {
      name: 'Remote MCP',
      description: 'Remote test server',
      enabled: true,
      assignments: ['model'],
      secret: { action: 'replace', value: 'secret-token-value' },
      transport: 'http',
      url: 'https://mcp.example.com/mcp'
    })
    const server = snapshot.mcpServers[0]
    expect(server).toMatchObject({
      name: 'Remote MCP',
      transport: 'http',
      secretConfigured: true
    })
    expect(JSON.stringify(snapshot)).not.toContain('secret-token-value')
    expect(await readFile(filePath, 'utf8')).not.toContain(
      'secret-token-value'
    )
    if (!server) {
      throw new Error('Expected saved MCP server')
    }
    await expect(
      service.getResolvedMcpServer(server.id)
    ).resolves.toMatchObject({
      secret: 'secret-token-value'
    })
  })

  it('stores stdio command and arguments as separate values', async () => {
    const { service } = await createService()
    const snapshot = await service.saveMcpServer(undefined, {
      name: 'Local MCP',
      description: '',
      enabled: true,
      assignments: ['model'],
      secret: { action: 'keep' },
      transport: 'stdio',
      command: 'node',
      args: ['server.js', '--safe']
    })

    expect(snapshot.mcpServers[0]).toMatchObject({
      transport: 'stdio',
      command: 'node',
      args: ['server.js', '--safe']
    })
  })

  it('allows bearer tokens over the full IPv4 loopback range', async () => {
    const { service } = await createService()

    await expect(
      service.saveMcpServer(undefined, {
        name: 'Loopback MCP',
        description: '',
        enabled: true,
        assignments: ['model'],
        secret: { action: 'replace', value: 'secret-token-value' },
        transport: 'http',
        url: 'http://127.0.0.2/mcp'
      })
    ).resolves.toMatchObject({
      mcpServers: [
        expect.objectContaining({
          name: 'Loopback MCP',
          url: 'http://127.0.0.2/mcp'
        })
      ]
    })
  })

  it('allows bearer tokens over HTTP on any configured host', async () => {
    const { service } = await createService()

    const snapshot = await service.saveMcpServer(undefined, {
      name: 'Intranet MCP',
      description: '',
      enabled: true,
      assignments: ['model'],
      secret: { action: 'replace', value: 'secret-token-value' },
      transport: 'http',
      url: 'http://mcp.internal/mcp'
    })
    expect(snapshot).toMatchObject({
      mcpServers: [
        expect.objectContaining({
          name: 'Intranet MCP',
          secretConfigured: true,
          url: 'http://mcp.internal/mcp'
        })
      ]
    })
    const server = snapshot.mcpServers[0]
    if (!server) {
      throw new Error('Expected saved intranet MCP server')
    }
    await expect(
      service.getResolvedMcpServer(server.id)
    ).resolves.toMatchObject({ secret: 'secret-token-value' })
  })

  it('allows public HTTP MCP servers with or without bearer tokens', async () => {
    const { service } = await createService()

    await expect(
      service.saveMcpServer(undefined, {
        name: 'Public plaintext MCP',
        description: '',
        enabled: true,
        assignments: ['model'],
        secret: { action: 'replace', value: 'secret-token-value' },
        transport: 'http',
        url: 'http://mcp.example.com/mcp'
      })
    ).resolves.toMatchObject({
      mcpServers: [
        expect.objectContaining({
          url: 'http://mcp.example.com/mcp',
          secretConfigured: true
        })
      ]
    })

    await expect(
      service.saveMcpServer(undefined, {
        name: 'Public MCP without token',
        description: '',
        enabled: true,
        assignments: ['model'],
        secret: { action: 'clear' },
        transport: 'http',
        url: 'http://mcp.example.com/no-token'
      })
    ).resolves.toMatchObject({
      mcpServers: expect.arrayContaining([
        expect.objectContaining({
          url: 'http://mcp.example.com/no-token',
          secretConfigured: false
        })
      ])
    })
  })

  it('rejects MCP assignments to Agent Runtimes', async () => {
    const { service } = await createService()

    await expect(
      service.saveMcpServer(undefined, {
        name: 'Agent MCP',
        description: '',
        enabled: true,
        assignments: ['opencode'],
        secret: { action: 'keep' },
        transport: 'stdio',
        command: 'node',
        args: ['server.js']
      })
    ).rejects.toThrow('只能分配给直连模型')
  })

  it('migrates legacy OpenCode MCP assignments to the direct model', async () => {
    const { filePath, builtinRoot, importedRoot } = await createService()
    await writeFile(
      filePath,
      JSON.stringify({
        version: 1,
        skills: {},
        mcpServers: [
          {
            id: 'd2ef774b-146c-4467-a909-6feb112a9c2c',
            name: 'Legacy MCP',
            description: '',
            enabled: true,
            assignments: ['opencode'],
            transport: 'stdio',
            command: 'node',
            args: ['server.js']
          }
        ]
      }),
      'utf8'
    )
    const service = new CapabilityService(
      filePath,
      builtinRoot,
      importedRoot,
      cipher
    )

    await expect(service.getSnapshot()).resolves.toMatchObject({
      mcpServers: [
        expect.objectContaining({ assignments: ['model'] })
      ]
    })
    expect(await readFile(filePath, 'utf8')).toContain(
      '"assignments": [\n        "model"'
    )
    await expect(service.getResolvedMcpServers('opencode')).resolves.toEqual([])
    await expect(service.getResolvedMcpServers('model')).resolves.toHaveLength(1)
  })

  it('migrates v1 to v2 without losing skills, MCP configuration, or encrypted secrets', async () => {
    const { filePath, builtinRoot, importedRoot } = await createService()
    const credential = Buffer.from(
      'encrypted:{"version":1,"serverId":"d2ef774b-146c-4467-a909-6feb112a9c2c","secret":"preserved-secret"}'
    ).toString('base64')
    await writeFile(
      filePath,
      JSON.stringify({
        version: 1,
        skills: {
          'document-writing': {
            enabled: false,
            assignments: ['model']
          }
        },
        mcpServers: [
          {
            id: 'd2ef774b-146c-4467-a909-6feb112a9c2c',
            name: 'Preserved MCP',
            description: 'migration',
            enabled: true,
            assignments: ['model'],
            credential: {
              formatVersion: 1,
              scheme: 'electron-safe-storage',
              ciphertextBase64: credential
            },
            transport: 'http',
            url: 'https://mcp.example.com/mcp'
          }
        ]
      }),
      'utf8'
    )
    const service = new CapabilityService(
      filePath,
      builtinRoot,
      importedRoot,
      cipher,
      {
        platform: 'win32',
        architecture: 'x64',
        electronTarget: true,
        browserProfiles: new BrowserProfileService(
          new MemoryBrowserProfileStore()
        )
      }
    )

    await expect(service.getSnapshot()).resolves.toMatchObject({
      skills: [
        expect.objectContaining({
          id: 'document-writing',
          enabled: false,
          assignments: ['model']
        })
      ],
      mcpServers: [
        expect.objectContaining({
          name: 'Preserved MCP',
          secretConfigured: true
        })
      ],
      computerCapabilities: [
        expect.objectContaining({
          id: 'host-browser-control',
          enabled: false
        }),
        expect.objectContaining({
          id: 'linux-desktop-control',
          enabled: false
        })
      ]
    })
    const persisted = await readFile(filePath, 'utf8')
    expect(persisted).toContain('"version": 2')
    expect(persisted).toContain(credential)
    expect(persisted).not.toContain('preserved-secret')
  })

  it('gates enablement on the supported platform and architecture', async () => {
    const { service } = await createService({
      platform: 'darwin',
      architecture: 'arm64',
      electronTarget: true,
      browserProfiles: new BrowserProfileService(
        new MemoryBrowserProfileStore()
      )
    })

    await expect(
      service.setComputerCapabilityEnabled(
        'linux-desktop-control',
        true
      )
    ).rejects.toThrow('不支持')
    const snapshot = await service.setComputerCapabilityEnabled(
      'host-browser-control',
      true
    )
    expect(snapshot.computerCapabilities).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          id: 'host-browser-control',
          enabled: true
        })
      ])
    )
  })

  it('maintains browser profile references and rejects unknown profiles', async () => {
    const { service } = await createService()
    await expect(
      service.setComputerCapabilityBrowserProfile(
        'host-browser-control',
        'b7f29e4c-1c4a-4aa0-ac58-5165451dde07'
      )
    ).rejects.toThrow('不存在')

    const created = await service.createBrowserProfile('隔离工作配置')
    const profileId = created.browserProfiles?.profiles[0]?.id
    if (!profileId) {
      throw new Error('Expected browser profile')
    }
    await service.setComputerCapabilityBrowserProfile(
      'host-browser-control',
      profileId
    )
    await expect(service.removeBrowserProfile(profileId)).rejects.toThrow(
      'Referenced'
    )
    await service.setComputerCapabilityBrowserProfile(
      'host-browser-control',
      null
    )
    await expect(service.removeBrowserProfile(profileId)).resolves.toMatchObject(
      {
        browserProfiles: { profiles: [], defaultProfileId: null }
      }
    )
  })

  it('compensates browser profile references when add or capability persistence fails', async () => {
    const addProfiles = new FailingBrowserProfileService(
      new MemoryBrowserProfileStore()
    )
    const addCase = await createService({
      platform: 'win32',
      architecture: 'x64',
      electronTarget: true,
      browserProfiles: addProfiles
    })
    const addCreated = await addCase.service.createBrowserProfile(
      '添加失败配置'
    )
    const addProfileId = addCreated.browserProfiles?.profiles[0]?.id
    if (!addProfileId) {
      throw new Error('Expected add-failure browser profile')
    }
    addProfiles.failNextAddAfterSave = true

    await expect(
      addCase.service.setComputerCapabilityBrowserProfile(
        'host-browser-control',
        addProfileId
      )
    ).rejects.toThrow('Injected add reference failure')
    await expect(
      addCase.service.removeBrowserProfile(addProfileId)
    ).resolves.toMatchObject({
      browserProfiles: { profiles: [], defaultProfileId: null }
    })

    const persistProfiles = new BrowserProfileService(
      new MemoryBrowserProfileStore()
    )
    const persistCase = await createService({
      platform: 'win32',
      architecture: 'x64',
      electronTarget: true,
      browserProfiles: persistProfiles
    })
    const persistCreated =
      await persistCase.service.createBrowserProfile('保存失败配置')
    const persistProfileId =
      persistCreated.browserProfiles?.profiles[0]?.id
    if (!persistProfileId) {
      throw new Error('Expected persistence-failure browser profile')
    }
    await mkdir(persistCase.filePath)

    await expect(
      persistCase.service.setComputerCapabilityBrowserProfile(
        'host-browser-control',
        persistProfileId
      )
    ).rejects.toThrow()
    await expect(
      persistCase.service.removeBrowserProfile(persistProfileId)
    ).resolves.toMatchObject({
      browserProfiles: { profiles: [], defaultProfileId: null }
    })
  })

  it('rolls back capability and profile stores when old-reference removal fails', async () => {
    const browserProfiles = new FailingBrowserProfileService(
      new MemoryBrowserProfileStore()
    )
    const { service } = await createService({
      platform: 'win32',
      architecture: 'x64',
      electronTarget: true,
      browserProfiles
    })
    const first = await service.createBrowserProfile('原配置')
    const firstId = first.browserProfiles?.profiles[0]?.id
    const second = await service.createBrowserProfile('新配置')
    const secondId = second.browserProfiles?.profiles[1]?.id
    if (!firstId || !secondId) {
      throw new Error('Expected two browser profiles')
    }
    await service.setComputerCapabilityBrowserProfile(
      'host-browser-control',
      firstId
    )
    browserProfiles.failNextRemoveAfterSave = true

    await expect(
      service.setComputerCapabilityBrowserProfile(
        'host-browser-control',
        secondId
      )
    ).rejects.toThrow('Injected remove reference failure')
    await expect(service.getSnapshot()).resolves.toMatchObject({
      computerCapabilities: expect.arrayContaining([
        expect.objectContaining({
          id: 'host-browser-control',
          browserProfileId: firstId
        })
      ])
    })
    await expect(service.removeBrowserProfile(secondId)).resolves.toBeDefined()
    await expect(service.removeBrowserProfile(firstId)).rejects.toThrow(
      'Referenced'
    )
  })

  it('redacts injected diagnostics and never claims browser availability outside Electron', async () => {
    process.env.GOODBUDDY_CAPABILITY_SERVICE_SECRET =
      'service-diagnostic-secret-value'
    const diagnostics = new CapabilityDiagnostics([
      {
        id: 'browser-executable',
        run: async () => ({
          status: 'available',
          summary:
            'token=visible-token service-diagnostic-secret-value C:\\Users\\Alice\\browser'
        })
      },
      {
        id: 'managed-profile-root',
        run: async () => ({
          status: 'available',
          summary: 'managed storage ready'
        })
      }
    ])
    const { service } = await createService({
      platform: 'win32',
      architecture: 'x64',
      electronTarget: true,
      browserProfiles: new BrowserProfileService(
        new MemoryBrowserProfileStore()
      ),
      diagnostics
    })
    await service.setComputerCapabilityEnabled('host-browser-control', true)
    const report = await service.diagnoseComputerCapability(
      'host-browser-control'
    )
    expect(report.status).toBe('available')
    expect(JSON.stringify(report)).not.toContain('visible-token')
    expect(JSON.stringify(report)).not.toContain(
      'service-diagnostic-secret-value'
    )

    const outsideElectron = await createService({
      platform: 'win32',
      architecture: 'x64',
      electronTarget: false,
      browserProfiles: new BrowserProfileService(
        new MemoryBrowserProfileStore()
      ),
      diagnostics
    })
    await expect(
      outsideElectron.service.setComputerCapabilityEnabled(
        'host-browser-control',
        true
      )
    ).rejects.toThrow('诊断不可用')
    await expect(
      outsideElectron.service.getComputerCapabilityStatus(
        'host-browser-control'
      )
    ).resolves.toEqual({ enabled: false, supported: true })
    delete process.env.GOODBUDDY_CAPABILITY_SERVICE_SECRET
  })

  it('keeps Linux desktop unavailable without a registered native adapter', async () => {
    const { service } = await createService({
      platform: 'linux',
      architecture: 'x64',
      electronTarget: true,
      browserProfiles: new BrowserProfileService(
        new MemoryBrowserProfileStore()
      )
    })

    await expect(
      service.setComputerCapabilityEnabled(
        'linux-desktop-control',
        true
      )
    ).rejects.toThrow('不支持')
    await expect(
      service.getComputerCapabilityStatus('linux-desktop-control')
    ).resolves.toEqual({ enabled: false, supported: false })
  })

  it('allows Linux desktop enablement with available injected diagnostics', async () => {
    const diagnostics = new CapabilityDiagnostics(
      ['linux-session', 'desktop-driver', 'desktop-permissions'].map(
        (id) => ({
          id,
          run: async () => ({
            status: 'available' as const,
            summary: `${id} ready`
          })
        })
      )
    )
    const { service } = await createService({
      platform: 'linux',
      architecture: 'arm64',
      electronTarget: true,
      browserProfiles: new BrowserProfileService(
        new MemoryBrowserProfileStore()
      ),
      diagnostics,
      availableComputerCapabilityImplementations: [
        'managed-browser-driver',
        'managed-linux-desktop-driver'
      ]
    })

    await expect(
      service.setComputerCapabilityEnabled(
        'linux-desktop-control',
        true
      )
    ).resolves.toMatchObject({
      computerCapabilities: expect.arrayContaining([
        expect.objectContaining({
          id: 'linux-desktop-control',
          enabled: true
        })
      ])
    })
  })
})
