import { afterEach, describe, expect, it, vi } from 'vitest'

const {
  isSelfContainedBinaryMock,
  resolveBinaryUpdateConfigMock,
  resolveBunTargetMock,
  getDimcodeUserDirMock,
  resolveCliVersionMock,
  readFileMock,
  semverGtMock,
  semverValidMock,
} = vi.hoisted(() => ({
  isSelfContainedBinaryMock: vi.fn(),
  resolveBinaryUpdateConfigMock: vi.fn(),
  resolveBunTargetMock: vi.fn(),
  getDimcodeUserDirMock: vi.fn(),
  resolveCliVersionMock: vi.fn(),
  readFileMock: vi.fn(),
  semverGtMock: vi.fn(),
  semverValidMock: vi.fn(),
}))

vi.mock('../src/dimsdk/cli/binary-upgrade.ts', () => ({
  isSelfContainedBinary: isSelfContainedBinaryMock,
  resolveBinaryUpdateConfig: resolveBinaryUpdateConfigMock,
  resolveBunTarget: resolveBunTargetMock,
}))

vi.mock('../src/dimsdk/cli/user-cache.ts', () => ({
  getDimcodeUserDir: getDimcodeUserDirMock,
}))

vi.mock('../src/dimsdk/cli/version.ts', () => ({
  resolveCliVersion: resolveCliVersionMock,
}))

vi.mock('node:fs/promises', () => ({
  readFile: readFileMock,
}))

vi.mock('semver', () => ({
  default: {
    gt: semverGtMock,
    valid: semverValidMock,
  },
}))

const { printCliVersionInfo } = await import('../src/dimsdk/cli/version-info.ts')

afterEach(() => {
  vi.restoreAllMocks()
  isSelfContainedBinaryMock.mockReset()
  resolveBinaryUpdateConfigMock.mockReset()
  resolveBunTargetMock.mockReset()
  getDimcodeUserDirMock.mockReset()
  resolveCliVersionMock.mockReset()
  readFileMock.mockReset()
  semverGtMock.mockReset()
  semverValidMock.mockReset()
})

describe('printCliVersionInfo', () => {
  it('prints installed binary metadata when the persisted payload is valid', async () => {
    const env = { HOME: '/tmp/home' }
    const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

    resolveCliVersionMock.mockReturnValue({
      version: '1.2.0',
      source: 'package',
    })
    isSelfContainedBinaryMock.mockReturnValue(true)
    resolveBunTargetMock.mockReturnValue('bun-linux-x64')
    resolveBinaryUpdateConfigMock.mockReturnValue({
      provider: 'github',
      project: 'nextim/goatagent',
      githubApiBaseUrl: 'https://api.github.com',
    })
    getDimcodeUserDirMock.mockResolvedValue('/tmp/dimcode')
    semverValidMock.mockReturnValue('1.3.0')
    semverGtMock.mockReturnValue(true)
    readFileMock.mockResolvedValue(
      JSON.stringify({
        version: 1,
        cliVersion: '1.3.0',
        binaryPath: '/tmp/dimcode/bin/dim',
      }),
    )

    await printCliVersionInfo(env)

    expect(resolveBinaryUpdateConfigMock).toHaveBeenCalledWith(env)
    expect(getDimcodeUserDirMock).toHaveBeenCalledWith(env)
    expect(readFileMock).toHaveBeenCalledWith(
      '/tmp/dimcode/bin/binary-upgrade.json',
      'utf8',
    )
    expect(logSpy.mock.calls.map(args => args[0])).toEqual(
      expect.arrayContaining([
        'dim 1.2.0 (package)',
        expect.stringMatching(/^execPath: .+/),
        'standalone: true',
        'target: bun-linux-x64',
        'update: provider=github project=nextim/goatagent',
        'update: githubApiBaseUrl=https://api.github.com',
        'installed: 1.3.0 at /tmp/dimcode/bin/dim',
        'installedIsNewer: true',
      ]),
    )
  })

  it('prints installed none when the persisted payload is not a valid v1 object', async () => {
    const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})

    resolveCliVersionMock.mockReturnValue({
      version: '1.2.0',
      source: 'package',
    })
    isSelfContainedBinaryMock.mockReturnValue(true)
    resolveBunTargetMock.mockReturnValue('bun-linux-x64')
    resolveBinaryUpdateConfigMock.mockReturnValue({
      provider: 'gitlab',
      host: 'gitlab.example.com',
      project: 'team/dim',
      githubApiBaseUrl: 'https://api.github.com',
    })
    getDimcodeUserDirMock.mockResolvedValue('/tmp/dimcode')
    semverValidMock.mockReturnValue('1.3.0')
    readFileMock.mockResolvedValue(
      JSON.stringify({
        version: 2,
        cliVersion: '1.3.0',
        binaryPath: '/tmp/dimcode/bin/dim',
      }),
    )

    await printCliVersionInfo({})

    expect(logSpy.mock.calls.map(args => args[0])).toContain(
      'installed: (none)',
    )
    expect(logSpy.mock.calls.map(args => args[0])).not.toContain(
      'installedIsNewer: true',
    )
  })
})
